int main(void) { char str[] = "0123456789"; int a, b, c; //atoi関数 手軽だが変換に失敗しても検出できない //10進数表記専用 a = atoi(str);//文字列の先頭のアドレスをint型の整数にしてaに代入する、 //なのでaの中身は123456789となる。 //strtol/strtoul関数 変換出来なかった文字へのポインタを監視することで変換失敗を検出できる //2~16進数に対応できる char* p;//ポインタにpをポインタ変数宣言する。 b = strtol(str, &p, 10);//0123456789の10個のstrtol //ポインタpの先頭のアドレスを引数に if (p == str) {//一文字も変換出来なかった、要は変換できたデータ数が0だった printf("変換失敗\n"); } //sscanf関数 関数の戻り値を監視することで変換失敗を検出できる //8,10,16進数に対応できる #pragma warning(suppress : 4996) int n = sscanf(str, "%d", &c); if (n == 0) {//変換できたデータ数が0だった printf("変換失敗\n"); } printf("%d %d %d", a, b, c); return 0; }
読解力がないせいでstrtolを調べても引数を使ってint型のbを返り値にするとしかわかりませんでした。
strは文字列を表し、&pはポインタpのアドレスを指す、10は0123456789の10個だと思いますが、この引数3つを使ってどうやってint型の123456789を導いたかの過程が知りたいです、
どうかわかりやすく例文などを道いて説明して頂けないでしょう、よろしくお願いいたします。
編集
b = strtol(str, &p, 10);//strからアドレスを読み取り、ポインタpのアドレスを知り、
//0123456789を引数にというとことまではわかりますが、この引数がどう働いてint型のbに代入される形になるかわかりません。
strtol(str, &p, 10);で引数を基に関数strtolの内部で処理して、その返り値をint型のbに代入する。。。
関数とはそういうものでなのですね。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/02/22 19:58
2021/02/22 20:02
2021/02/23 00:25 編集
2021/02/23 00:50
2021/02/23 02:02
2021/02/23 02:03
2021/02/23 02:17
退会済みユーザー
2021/02/24 00:51
回答4件
0
この引数3つを使ってどうやってint型の123456789を導いたかの過程が知りたいです
逆質問にはなりますが、何のために知りたいのですか?
むしろ、「指示された通りの引数を渡せば期待通りの結果を返す」ので中身を考えなくてもいいというのが、ライブラリ関数のメリットだと思っていたのですが、carnage0216さんにとってはそうではないのでしょうか?
投稿2021/02/22 22:35
総合スコア146018
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/02/23 02:10 編集
2021/02/23 02:19
0
質問への回答ではありませんが、
http://www.ethernut.de/api/strtol_8c_source.html
episteme さんが質問への追記・修正で、 strtol のソースだと示されたものです。
そのコードにはバグがあります。
VC++ のように LONG_MAX が 2147483647 の環境で
C
1int main(void) 2{ 3 char *p; 4 const char *s; 5 long v; 6 7 s = "2147483650"; 8 v = Strtol(s, &p, 10); 9 printf("s=%p, s=[%s]\n", s, s); 10 printf("p=%p, v=%ld, errno=%d\n", p, v, errno); 11 12 s = "2147483648"; 13 v = Strtol(s, &p, 10); 14 printf("s=%p, s=[%s]\n", s, s); 15 printf("p=%p, v=%ld, errno=%d\n", p, v, errno); 16}
実行結果
text
1s=00007FF6400BD000, s=[2147483650] 2p=00007FF6400BD00A, v=-2147483646, errno=0 3s=00007FF6400BD038, s=[2147483648] 4p=00007FF6400BD042, v=2147483647, errno=34
LONG_MAX より 3 大きい 2147483650 は errno=0 で結果が -2147483646
LONG_MAX より 1 大きい 2147483658 は errno=34 で結果が 2147483647
gcc のように LONG_MAX が 9223372036854775807 の環境でも同様な実行結果になります。
C
1 s = "9223372036854775810"; 2 s = "9223372036854775808";
実行結果
text
1s=0x7f9533b79004, s=[9223372036854775810] 2p=0x7f9533b79017, v=-9223372036854775806, errno=0 3s=0x7f9533b7903d, s=[9223372036854775808] 4p=0x7f9533b79050, v=9223372036854775807, errno=34
コードのバグは 180行目で、修正方法は、
diff
1- if ((acc > cutoff || acc == cutoff) && c > cutlim) { 2+ if (acc > cutoff || acc == cutoff && c > cutlim) {
170行目にも同様の修正が必要です。
追記
質問への回答です。
char str[5] = "0123", *p; で
int b = strtol(str, &p, 10); の場合を説明します。
str は str[0] を指すポインタ です。str の値 は str[0] のアドレス です。
&p は p を指すポインタ です。&p の値 は p のアドレス です。
10 は基数です。文字列が 10進数だとして int に変換してほしいということです。
これらの引数を用意して、main から strtol を呼び出すと、
呼び出された strtol は、次のように動きます。
・str[0] へのポインタがあるので、
str[0] の値である '0' を得て、そこから '0' を引いて int の 0 を得ます。
・str[0] へのポインタがあるので、次の str[1] の値 '1' から 1 を得ます。
・最初に憶えた int の値 0 を 10倍して、1 を足すと 1 になります。
・str[0] へのポインタがあるので、次の str[2] の値 '2' から 2 を得ます。
・覚えている int の値 1 を 10倍して、2 を足すと 12 になります。
・str[0] へのポインタがあるので、次の str[3] の値 '3' から 3 を得ます。
・覚えている int の値 12 を 10倍して、3 を足すと 123 になります。
・str[0] へのポインタがあるので、次の str[4] の値 '\0' を得ます。
これは数字ではないので変換終了。
・p へのポインタがあるので、p に str[4] のアドレスを入れます。
・int の 123 を返します。
main に返ると、strtol が返した 123 という値で b を初期化します。
これが strtol の動きを具体的に説明したものです。
基数が 10以外の場合どうなるか、次のコードで考えてみてください。
C
1#include <stdio.h> // printf 2#include <stdlib.h> // strtol 3 4int main(void) 5{ 6 char str[] = "0123"; 7 char *p; 8 int i; 9 printf(" str = %p, str = [%s]\n", str, str); 10 i = strtol(str, &p, 16); printf("16: p = %p: i = %d\n", p, i); 11 i = strtol(str, &p, 10); printf("10: p = %p: i = %d\n", p, i); 12 i = strtol(str, &p, 8); printf(" 8: p = %p: i = %d\n", p, i); 13 i = strtol(str, &p, 2); printf(" 2: p = %p: i = %d\n", p, i); 14 i = strtol(str, &p, 0); printf(" 0: p = %p: i = %d\n", p, i); 15}
実行結果
str = 0x7fffd58dd6a3, str = [0123] 16: p = 0x7fffd58dd6a7: i = 291 10: p = 0x7fffd58dd6a7: i = 123 8: p = 0x7fffd58dd6a7: i = 83 2: p = 0x7fffd58dd6a5: i = 1 0: p = 0x7fffd58dd6a7: i = 83
投稿2021/02/23 09:27
編集2021/02/24 07:37総合スコア8224
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/02/23 09:39
2021/02/24 04:37 編集
0
関数とはそういうものでなのですね。
はいそうですね、以外の回答がないわけですが何を聞きたいのでしょう?
strtol系関数の適切な呼び出し方については
C言語で安全に標準入力から数値を取得 - Qiita
もどうぞ
とにかくmanページを読む癖をつけて下さい。そのうえでこの解説のこの部分がわからんとかであればまだ回答のしようがあります。全くわからないのだとしたら基礎知識が欠けていますから、適当な解説本を一通り読んで見るべきでしょう。前橋和弥氏の「C言語ポインタ完全制覇」を私からはおすすめしておきますが。
C言語では関数から複数のオブジェクトを返すには工夫が必要です。
- すべてのオブジェクトを含んだ構造体を返す
- 一つは戻り値として、残りは引数経由で返す
- なんらかのグローバルな、あるいはスレッドローカルな変数を用いる
strtol
系関数はこの2番目と3番目の技法が使われています。2番目についてもうちょっと一般化した例を見てみることにします。
c
1#include <stdio.h> 2int foo(int* out) 3{ 4 if (NULL != out) { 5 *out = 23; 6 return 52; 7 } 8 else { 9 return 0; 10 } 11} 12int main(void) 13{ 14 int ret2; 15 const int ret1 = foo(&ret2); 16 printf("ret1=%d, ret2=%d", ret1, ret2);// => ret1=52, ret2=23 17}
上の例で関数foo
から返却される値は戻り値のほかに引数out
が指し示す領域を書き換えることで実現されるものがあります。今回の関数foo
の呼び出しでは、呼び出し元(main
関数)でint
型の変数ret2
を用意し、それへのポインタを取得します(&ret2
)。これを関数foo
の引数に渡していますね。こうすることで関数foo
の引数out
は呼び出し元(main
関数)のint
型の変数ret2
を指し示すポインタとなっています。ここで*out = 23;
のようにポインタをデレファレンスすることで、呼び出し元(main
関数)のint
型の変数ret2
を書き換えられるのでした。
ではstrtol系関数に近づけてみましょう。簡単のためにunsigned intにパースすることを考え、かつ間違っても天と地がひっくり返っても実際にこのコードが使われることがないように祈りを込めて関数名も「劣った」を意味するinferior
と「安全ではない」を意味するunsafe
を含めておきました。
c
1unsigned int inferior_unsafe_strtoui(char const* p, char const** endptr/*, int base 10進数を仮定するので省略 */) 2{ 3 bool conversion_started = false; 4 unsigned int ret = 0; 5 size_t i; 6 // endptrを通じて呼び出し元の値を書き換える。「2. 一つは戻り値として、残りは引数経由で返す」に該当 7 if(endptr) *endptr = p; 8 for( 9 i = 0; 10 // null文字終端 11 '\0' != p[i] 12 && ( 13 // -から始まる場合はstrtoul関数同様符号反転させるため読み飛ばす。+から始まる場合は読み飛ばす 14 NULL != strchr("+-", p[i]) 15 // 先頭の空白は読み飛ばす 16 || (!conversion_started && isspace(p[i])) 17 // C規格では文字コード上で0~9が連続することを保証する 18 || ('0' <= p[i] && p[i] <= '9') 19 ); 20 ++i 21 ) { 22 // 先頭の空白は読み飛ばす 23 if (!conversion_started && isspace(p[i])) continue; 24 // 空白以外が来たのでフラグを変更 25 conversion_started = true; 26 // +-は読み飛ばす。for文の条件よりp[i]はNULL文字ではない 27 if (NULL != strchr("+-", p[i])) continue; 28 // retを10倍できないとき 29 if (UINT_MAX / 10 < ret) { 30 // 結果の値が範囲外である 31 // errnoはglobalないしthread local変数なので、「3. なんらかのグローバルな、あるいはスレッドローカルな変数を用いる」に該当 32 errno = ERANGE; 33 return UINT_MAX; 34 } 35 ret *= 10; 36 // 文字から整数への変換 37 const unsigned int digit = p[i] - '0'; 38 if (UINT_MAX - digit < ret) { 39 // 結果の値が範囲外である 40 // errnoはglobalないしthread local変数なので、「3. なんらかのグローバルな、あるいはスレッドローカルな変数を用いる」に該当 41 errno = ERANGE; 42 return UINT_MAX; 43 } 44 ret += digit; 45 } 46 // endptrを通じて呼び出し元の値を書き換える。「2. 一つは戻り値として、残りは引数経由で返す」に該当 47 if(endptr) *endptr = p + i; 48 return ret; 49}
https://wandbox.org/permlink/urDikZ0Nn7YyAceN
これでstrtolがいかにして値を返却しているかわかるかと思います。
投稿2021/02/22 21:46
編集2021/02/23 05:21総合スコア5852
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
まず、引数10は10進数だからです。たとえば、16進数ならば16とします。当然、この数値次第で戻り値が変わります。たとえば、「10」という文字列は基数によって数字としての意味が変わります。
そして引数pは意味がありません。これは読み取れない記号があった時に意味を持ちます。たとえば、「12」という文字列を2進数として読み取ろうとした場合に意味が現れます。
投稿2021/02/22 20:54
総合スコア4830
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/02/22 21:07
2021/02/23 02:10 編集
2021/02/23 02:15
2021/02/23 08:35 編集
2021/02/23 03:23
2021/02/23 03:28
2021/02/23 03:33
2021/02/23 03:41
2021/02/23 04:06
2021/02/23 04:08
2021/02/23 04:11
2021/02/23 04:13
2021/02/23 04:32 編集
2021/02/23 11:03 編集
2021/02/23 05:01
2021/02/23 05:05 編集
2021/02/23 05:24
2021/02/23 06:54 編集
2021/02/23 06:56
2021/02/23 07:08
2021/02/23 15:11 編集
2021/02/23 08:46 編集
2021/02/23 12:38
2021/02/24 03:55 編集
2021/02/24 03:57
2021/02/24 04:02
2021/02/24 04:15
2021/02/24 04:53
2021/02/24 04:58
2021/02/24 05:17
退会済みユーザー
2021/02/24 05:39 編集
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。