質問するログイン新規登録

回答編集履歴

1

m

2021/02/23 05:21

投稿

yumetodo
yumetodo

スコア5852

answer CHANGED
@@ -4,4 +4,98 @@
4
4
 
5
5
  strtol系関数の適切な呼び出し方については
6
6
  [C言語で安全に標準入力から数値を取得 - Qiita](https://qiita.com/yumetodo/items/238751b879c09b56234b)
7
- もどうぞ
7
+ もどうぞ
8
+
9
+ ---
10
+
11
+ とにかくmanページを読む癖をつけて下さい。そのうえでこの解説のこの部分がわからんとかであればまだ回答のしようがあります。全くわからないのだとしたら基礎知識が欠けていますから、適当な解説本を一通り読んで見るべきでしょう。前橋和弥氏の「C言語ポインタ完全制覇」を私からはおすすめしておきますが。
12
+
13
+ ---
14
+
15
+ C言語では関数から複数のオブジェクトを返すには工夫が必要です。
16
+
17
+ 1. すべてのオブジェクトを含んだ構造体を返す
18
+ 2. 一つは戻り値として、残りは引数経由で返す
19
+ 3. なんらかのグローバルな、あるいはスレッドローカルな変数を用いる
20
+
21
+ `strtol`系関数はこの2番目と3番目の技法が使われています。2番目についてもうちょっと一般化した例を見てみることにします。
22
+
23
+ ```c
24
+ #include <stdio.h>
25
+ int foo(int* out)
26
+ {
27
+ if (NULL != out) {
28
+ *out = 23;
29
+ return 52;
30
+ }
31
+ else {
32
+ return 0;
33
+ }
34
+ }
35
+ int main(void)
36
+ {
37
+ int ret2;
38
+ const int ret1 = foo(&ret2);
39
+ printf("ret1=%d, ret2=%d", ret1, ret2);// => ret1=52, ret2=23
40
+ }
41
+ ```
42
+
43
+ 上の例で関数`foo`から返却される値は戻り値のほかに引数`out`が指し示す領域を書き換えることで実現されるものがあります。今回の関数`foo`の呼び出しでは、呼び出し元(`main`関数)で`int`型の変数`ret2`を用意し、それへのポインタを取得します(`&ret2`)。これを関数`foo`の引数に渡していますね。こうすることで関数`foo`の引数`out`は呼び出し元(`main`関数)の`int`型の変数`ret2`を指し示すポインタとなっています。ここで`*out = 23;`のようにポインタをデレファレンスすることで、呼び出し元(`main`関数)の`int`型の変数`ret2`を書き換えられるのでした。
44
+
45
+ ではstrtol系関数に近づけてみましょう。簡単のためにunsigned intにパースすることを考え、かつ間違っても天と地がひっくり返っても実際にこのコードが使われることがないように祈りを込めて関数名も「劣った」を意味する`inferior`と「安全ではない」を意味する`unsafe`を含めておきました。
46
+
47
+ ```c
48
+ unsigned int inferior_unsafe_strtoui(char const* p, char const** endptr/*, int base 10進数を仮定するので省略 */)
49
+ {
50
+ bool conversion_started = false;
51
+ unsigned int ret = 0;
52
+ size_t i;
53
+ // endptrを通じて呼び出し元の値を書き換える。「2. 一つは戻り値として、残りは引数経由で返す」に該当
54
+ if(endptr) *endptr = p;
55
+ for(
56
+ i = 0;
57
+ // null文字終端
58
+ '\0' != p[i]
59
+ && (
60
+ // -から始まる場合はstrtoul関数同様符号反転させるため読み飛ばす。+から始まる場合は読み飛ばす
61
+ NULL != strchr("+-", p[i])
62
+ // 先頭の空白は読み飛ばす
63
+ || (!conversion_started && isspace(p[i]))
64
+ // C規格では文字コード上で0~9が連続することを保証する
65
+ || ('0' <= p[i] && p[i] <= '9')
66
+ );
67
+ ++i
68
+ ) {
69
+ // 先頭の空白は読み飛ばす
70
+ if (!conversion_started && isspace(p[i])) continue;
71
+ // 空白以外が来たのでフラグを変更
72
+ conversion_started = true;
73
+ // +-は読み飛ばす。for文の条件よりp[i]はNULL文字ではない
74
+ if (NULL != strchr("+-", p[i])) continue;
75
+ // retを10倍できないとき
76
+ if (UINT_MAX / 10 < ret) {
77
+ // 結果の値が範囲外である
78
+ // errnoはglobalないしthread local変数なので、「3. なんらかのグローバルな、あるいはスレッドローカルな変数を用いる」に該当
79
+ errno = ERANGE;
80
+ return UINT_MAX;
81
+ }
82
+ ret *= 10;
83
+ // 文字から整数への変換
84
+ const unsigned int digit = p[i] - '0';
85
+ if (UINT_MAX - digit < ret) {
86
+ // 結果の値が範囲外である
87
+ // errnoはglobalないしthread local変数なので、「3. なんらかのグローバルな、あるいはスレッドローカルな変数を用いる」に該当
88
+ errno = ERANGE;
89
+ return UINT_MAX;
90
+ }
91
+ ret += digit;
92
+ }
93
+ // endptrを通じて呼び出し元の値を書き換える。「2. 一つは戻り値として、残りは引数経由で返す」に該当
94
+ if(endptr) *endptr = p + i;
95
+ return ret;
96
+ }
97
+ ```
98
+
99
+ [https://wandbox.org/permlink/urDikZ0Nn7YyAceN](https://wandbox.org/permlink/urDikZ0Nn7YyAceN)
100
+
101
+ これでstrtolがいかにして値を返却しているかわかるかと思います。