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

回答編集履歴

12

修正

2018/06/23 17:04

投稿

LouiS0616
LouiS0616

スコア35678

answer CHANGED
@@ -90,7 +90,7 @@
90
90
  break;
91
91
  }
92
92
  if(ret == EOF) {
93
- fprintf(stderr, "Failed: Input must be ended by newline.\n");
93
+ fprintf(stderr, "Failed: Input must end with a linefeed.\n");
94
94
  break;
95
95
  }
96
96
  }
@@ -161,8 +161,6 @@
161
161
  }
162
162
  ```
163
163
 
164
- **追記:** バグ発見。文字列の途中にEOFを入れ込むと凍ります。
165
-
166
164
  『不正な入力がない』と仮定できるなら、これは次のコードに置き換えられます。
167
165
  ```C
168
166
  #include <stdio.h>

11

修正:ちょっとだけ改善

2018/06/23 17:04

投稿

LouiS0616
LouiS0616

スコア35678

answer CHANGED
@@ -83,9 +83,18 @@
83
83
  *nl = '\0';
84
84
  }
85
85
  else {
86
+ while(1) {
87
+ int ret = getchar();
86
- while(getchar() != '\n');
88
+ if(ret == '\n') {
89
+ fprintf(stderr, "Failed: Too long input.\n");
90
+ break;
91
+ }
92
+ if(ret == EOF) {
93
+ fprintf(stderr, "Failed: Input must be ended by newline.\n");
94
+ break;
95
+ }
96
+ }
87
97
 
88
- fprintf(stderr, "Failed: Too long input.\n");
89
98
  exit(1);
90
99
  }
91
100
 
@@ -135,6 +144,15 @@
135
144
  }
136
145
 
137
146
  type = *begin++;
147
+
148
+ // 文字コードが連続していると仮定:
149
+ // http://www.kijineko.co.jp/tech/superstitions/A-to-Z-is-sequence.html
150
+ if ('A' <= type && type <= 'Z');
151
+ else if('a' <= type && type <= 'z');
152
+ else {
153
+ fprintf(stderr, "Failed: Invalid type character '%c'.\n", type);
154
+ exit(1);
155
+ }
138
156
  }
139
157
 
140
158
  printf("num: %d, type: %c\n", num, type);

10

追記

2018/06/23 17:01

投稿

LouiS0616
LouiS0616

スコア35678

answer CHANGED
@@ -26,6 +26,11 @@
26
26
  業務で使うコードでは避けるべきです。しかし、個人で使う分には無視して良いように思います。
27
27
  悪意を持った人でない限りそんな入力しないからです。
28
28
 
29
+ 入力の最大桁数を決め打てるのなら、次のように書いても回避できます。
30
+ ```C
31
+ scanf("%2d%c", &num, &c);
32
+ ```
33
+
29
34
  ---
30
35
  **バッファに食べ残しができること**
31
36
  `10ab`と入力すると`b\n`が、`10a`と入力すると`\n`がバッファに残ります。
@@ -138,6 +143,8 @@
138
143
  }
139
144
  ```
140
145
 
146
+ **追記:** バグ発見。文字列の途中にEOFを入れ込むと凍ります。
147
+
141
148
  『不正な入力がない』と仮定できるなら、これは次のコードに置き換えられます。
142
149
  ```C
143
150
  #include <stdio.h>

9

修正

2018/06/22 17:20

投稿

LouiS0616
LouiS0616

スコア35678

answer CHANGED
@@ -17,14 +17,14 @@
17
17
 
18
18
  scanfを使う際に注意すべきことについて
19
19
  ---
20
- 上記で示したコードで、危険が生じ得る部分は次の二か所です。
20
+ 上記コードで、危険が生じ得る部分は次の二か所です。
21
21
 
22
22
  ---
23
23
  **オーバーフローを感知できないこと**
24
24
  例えば`10000000000000000000000000a`などという入力に対しては未定義の動作となります。
25
25
 
26
26
  業務で使うコードでは避けるべきです。しかし、個人で使う分には無視して良いように思います。
27
- 悪意を持った誰かでない限りそんな入力しないからです。
27
+ 悪意を持ったでない限りそんな入力しないからです。
28
28
 
29
29
  ---
30
30
  **バッファに食べ残しができること**

8

修正

2018/06/22 16:33

投稿

LouiS0616
LouiS0616

スコア35678

answer CHANGED
@@ -125,7 +125,7 @@
125
125
 
126
126
  // parse type
127
127
  if(strlen(begin) != 1) {
128
- fprintf(stderr, "Failed: Invalid format.");
128
+ fprintf(stderr, "Failed: Invalid format.\n");
129
129
  exit(1);
130
130
  }
131
131
 

7

追記

2018/06/22 14:05

投稿

LouiS0616
LouiS0616

スコア35678

answer CHANGED
File without changes

6

追記

2018/06/22 14:04

投稿

LouiS0616
LouiS0616

スコア35678

answer CHANGED
File without changes

5

修正

2018/06/22 14:04

投稿

LouiS0616
LouiS0616

スコア35678

answer CHANGED
@@ -90,12 +90,13 @@
90
90
  }
91
91
  }
92
92
 
93
- // parse integer
94
- char *begin = buffer;
95
93
  int num;
94
+ char type;
96
95
  {
96
+ char *begin = buffer;
97
97
  char *end;
98
+
98
-
99
+ // parse integer
99
100
  errno = 0;
100
101
  const long sl = strtol(begin, &end, 10);
101
102
 
@@ -121,11 +122,8 @@
121
122
 
122
123
  num = sl;
123
124
  begin = end;
124
- }
125
125
 
126
- // parse type
126
+ // parse type
127
- char type;
128
- {
129
127
  if(strlen(begin) != 1) {
130
128
  fprintf(stderr, "Failed: Invalid format.");
131
129
  exit(1);

4

追記

2018/06/22 13:45

投稿

LouiS0616
LouiS0616

スコア35678

answer CHANGED
@@ -45,4 +45,114 @@
45
45
  - fgets + strtol でがっつりセキュアなコードを書くか
46
46
  - scanf でお手軽に書くか
47
47
 
48
- どっちかじゃないですかね。中途半端なのが一番良くないと思います。
48
+ どっちかじゃないですかね。中途半端なのが一番良くないと思います。
49
+
50
+ 書いてみた:比較的安全なコード
51
+ ---
52
+ 調べながら書きましたが、これでも見落としがある気しかしないです。
53
+
54
+ **参考**
55
+ - [CERT C コーディングスタンダード](https://www.jpcert.or.jp/sc-rules/)
56
+ - [C言語関数辞典](http://www.c-tipsref.com/)
57
+
58
+ ```C
59
+ #include <ctype.h>
60
+ #include <errno.h>
61
+ #include <stdio.h>
62
+ #include <stdlib.h>
63
+ #include <string.h>
64
+
65
+ int main(void) {
66
+ char buffer[14 +1+1] = {0}; // + newline + null
67
+
68
+ // get input
69
+ {
70
+ char *ret = fgets(buffer, sizeof(buffer), stdin);
71
+ if(ret == NULL) {
72
+ fprintf(stderr, "Failed: At fgets.\n");
73
+ exit(1);
74
+ }
75
+
76
+ char *nl = strchr(buffer, '\n');
77
+ if(nl) {
78
+ *nl = '\0';
79
+ }
80
+ else {
81
+ while(getchar() != '\n');
82
+
83
+ fprintf(stderr, "Failed: Too long input.\n");
84
+ exit(1);
85
+ }
86
+
87
+ if(strlen(buffer) == 0) {
88
+ fprintf(stderr, "Failed: Empty input.\n");
89
+ exit(1);
90
+ }
91
+ }
92
+
93
+ // parse integer
94
+ char *begin = buffer;
95
+ int num;
96
+ {
97
+ char *end;
98
+
99
+ errno = 0;
100
+ const long sl = strtol(begin, &end, 10);
101
+
102
+ if(begin == end) {
103
+ fprintf(stderr, "Failed: Integer parsing.\n");
104
+ exit(1);
105
+ }
106
+ if(
107
+ // NOTE: これ errno 見るだけじゃダメなのだろうか...?
108
+ (sl == LONG_MIN || sl == LONG_MAX) && errno == ERANGE
109
+ ) {
110
+ fprintf(stderr, "Failed: Overflow is occurred.\n");
111
+ exit(1);
112
+ }
113
+ if(sl < INT_MIN) {
114
+ fprintf(stderr, "Failed: %ld is less than INT_MIN\n", sl);
115
+ exit(1);
116
+ }
117
+ if(INT_MAX < sl) {
118
+ fprintf(stderr, "Failed: %ld is greater than INT_MAX\n", sl);
119
+ exit(1);
120
+ }
121
+
122
+ num = sl;
123
+ begin = end;
124
+ }
125
+
126
+ // parse type
127
+ char type;
128
+ {
129
+ if(strlen(begin) != 1) {
130
+ fprintf(stderr, "Failed: Invalid format.");
131
+ exit(1);
132
+ }
133
+
134
+ type = *begin++;
135
+ }
136
+
137
+ printf("num: %d, type: %c\n", num, type);
138
+
139
+ return 0;
140
+ }
141
+ ```
142
+
143
+ 『不正な入力がない』と仮定できるなら、これは次のコードに置き換えられます。
144
+ ```C
145
+ #include <stdio.h>
146
+
147
+ int main(void) {
148
+ int num;
149
+ char type;
150
+
151
+ scanf("%d%c", &num, &type);
152
+ printf("num: %d, type: %c\n", num, type);
153
+
154
+ return 0;
155
+ }
156
+ ```
157
+
158
+ これに気休めでちょっとだけエラーチェックを加えると、回答先頭のコードになるわけです。

3

追記

2018/06/22 13:34

投稿

LouiS0616
LouiS0616

スコア35678

answer CHANGED
@@ -13,4 +13,36 @@
13
13
  }
14
14
  ```
15
15
 
16
- `10ab`というような入力は考慮していませんのでご注意ください。
16
+ `10ab`というような入力は考慮していませんのでご注意ください。
17
+
18
+ scanfを使う際に注意すべきことについて
19
+ ---
20
+ 上記で示したコードで、危険が生じ得る部分は次の二か所です。
21
+
22
+ ---
23
+ **オーバーフローを感知できないこと**
24
+ 例えば`10000000000000000000000000a`などという入力に対しては未定義の動作となります。
25
+
26
+ 業務で使うコードでは避けるべきです。しかし、個人で使う分には無視して良いように思います。
27
+ 悪意を持った誰かでない限りそんな入力しないからです。
28
+
29
+ ---
30
+ **バッファに食べ残しができること**
31
+ `10ab`と入力すると`b\n`が、`10a`と入力すると`\n`がバッファに残ります。
32
+ 入力処理を何度かにわたって行う場合に注意が必要です。
33
+
34
+ 標準入力をクリアするためのコードとして`fflush(stdin)`がよく紹介されています。
35
+ しかし、これは規格上未定義の動作を引き起こす(らしい)ので、使うべきでないです。
36
+ [きじねこ - [迷信] fflush で入力バッファをクリア](http://www.kijineko.co.jp/tech/superstitions/fflush-with-input-stream.html)
37
+
38
+ 改行文字まで空読みするのが確実な方法です。
39
+ ```C
40
+ while(getchar() != '\n');
41
+ ```
42
+
43
+ 結局のところ
44
+ ---
45
+ - fgets + strtol でがっつりセキュアなコードを書くか
46
+ - scanf でお手軽に書くか
47
+
48
+ どっちかじゃないですかね。中途半端なのが一番良くないと思います。

2

再送

2018/06/22 11:45

投稿

LouiS0616
LouiS0616

スコア35678

answer CHANGED
File without changes

1

追記

2018/06/21 14:04

投稿

LouiS0616
LouiS0616

スコア35678

answer CHANGED
@@ -3,7 +3,7 @@
3
3
  int num;
4
4
  char ch;
5
5
 
6
- int ret = scanf("%d%c", &num, &ch);
6
+ int ret = scanf("%d%c", &num, &ch); // 一時変数retは省いても良いが、可読性はやや落ちる
7
7
  if(ret != 2) {
8
8
  exit(1);
9
9
  }