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

回答編集履歴

3

scanf() 対策を追加

2020/03/01 22:59

投稿

rubato6809
rubato6809

スコア1382

answer CHANGED
@@ -283,4 +283,19 @@
283
283
  K[] という配列名は #define K 128*2 と被ります。コードの可読性・保守性の観点から、両方の「K」という名前をそれぞれ変更したほうが良いと思います。
284
284
 
285
285
  ・・・と、これだけでも、他の人が使えるコード品質になるには道のり長そうですね。
286
- Enjoy !
286
+ Enjoy !
287
+
288
+ > scanfを入れているのは、表示を確認するために一時停止をしたいから
289
+
290
+ そのためにグローバル変数に読み込むのは本末転倒・言語道断。その後 fwrite() はどうなるか考えよ。
291
+ scanf() で停止するなら <stdio.h> の入力関数 fgets() 等でも停まります。getchar(); とだけ書いたこともあるけど、普通は小さな関数を作って、停めたい所で呼ぶでしょう。
292
+ ```C
293
+ // extern int n; ← NG! 現状、こうなっている
294
+ void wait4keyinput(void)
295
+ {
296
+ int n; // 読み込む変数はローカルに取るべし
297
+ printf(" (hit return) "); // 何か表示させたほうが良いだろう
298
+ fflush(stdout); // just in case
299
+ scanf("%d", &n); // fgets(line, LINESIZE, stdin); という手も
300
+ }
301
+ ```

2

コードレビュー、さらに追加

2020/03/01 22:59

投稿

rubato6809
rubato6809

スコア1382

answer CHANGED
@@ -199,4 +199,88 @@
199
199
  #endif
200
200
  ```
201
201
  そもそも引数の f は、構造体そのものを引数にするのではなく、元の構造体へのポインタ(アドレス)だけ受け取れば十分なのではないか。構造体を引数にすると、実引数から仮引数へコピーが毎回発生するので効率悪い。
202
- (もう寝るw)
202
+ (もう寝るw)
203
+
204
+ ---
205
+ さらに追加します。
206
+
207
+ - 実際、不要なローカル変数が山のよう・・・
208
+
209
+ - 関数の最後にあるべき return 文が無く、関数の最後で目的の値を返さない経路がある。既に、上で int deg(vec a) 関数を指摘したが、他にもあった。コード品質の低さを示すもの。
210
+
211
+ unsigned short oinv(unsigned short a) // BUG !!!
212
+ unsigned char chk(OP f)
213
+ int isqrt(unsigned short u)
214
+ OP osqrt(OP f, OP w) --- 長大な関数
215
+
216
+ - 信用を失う滅茶苦茶な関数w
217
+ ```C
218
+ OP ToHorner(OP f)
219
+ {
220
+ vec v = o2v(f); // この計算は何?
221
+ OP h;
222
+ return h; // 不定値を返す OMG
223
+ }
224
+ ```
225
+
226
+ - 変数に代入するが使わない。何をするつもりか意図が不明な処理が多数見つかる。
227
+
228
+ ```C
229
+ // 計算結果を使わない例1
230
+ int main(void) // argc, argv を使わない
231
+ {
232
+ unsigned short mm[T]; // mm[t] = {0} 初期化不要
233
+ // 途中省略
234
+ for (i = 0; i < T; i++) {
235
+ mm[i] = r.t[i].a; // mm[i] に代入するも、使わない
236
+
237
+ // 計算結果を使わない例2
238
+ OP ogcd(OP f, OP g) // ユークリッドの互除法?
239
+ {
240
+ OP h, ww; // 初期化不要
241
+
242
+ for (int i = 0; i < T; i++) {
243
+ h = omod(f, g);
244
+ ww = odiv(f, g); // この除算は不要
245
+ f = g;
246
+ g = h;
247
+ }
248
+ // 一方、xgcd() は除算した結果 ww を使う。そこをコピペしたか?
249
+ ```
250
+
251
+ lu.c を拝見しました。
252
+ - 一文字のグローバル変数が!言語道断モノですw
253
+ ```C
254
+ int i, j, k; // カウンタ !!! OMG !!!
255
+ int n = F; // 配列の次数 ??? for fwrite()
256
+ ```
257
+ ここをコメントアウトすると、これらに頼っているコードが判明します。
258
+ この小文字変数 k に頼るコードが det() 関数に見つかります。
259
+ ```C
260
+ void det(unsigned short g[])
261
+ {
262
+ // 省略
263
+ k = cc[K];
264
+ // 途中省略
265
+ cc[K] = k;
266
+ ```
267
+ この大文字「K」は #define K 128*2 という定数マクロです。大文字・小文字のK, kを一緒に使うのは、いかがなものかと(乱視が悪化してきた私は)思う。
268
+
269
+ i, j, k は、頼っている関数にローカル変数を追加すれば削除できます。
270
+ 問題は n です。n の主目的は fwrite(dd, 1, n, fq); の引数とみられますが、他の箇所から n の値を変更可能です。たとえば oplib.c に scanf("%d", &n); が何箇所かあり、(意図通りかどうかは不明だが)キー入力した値がこの n に読み込まれるらしい。グローバル変数に対する典型的な戒めを変数「n」に見出すことができます。
271
+ しかも「n」という変数名は、関数内のローカル変数にもあるうえに、oterm 構造体のメンバ変数でもある。カオスです。
272
+
273
+ グローバル変数、マクロ名、寿命の長い変数、アルゴリズム的・意味的に重要な変数などはもっと長い変数名にしたほうが良いでしょう。ひとつの目安は grep コマンドで、それぞれの変数が登場する箇所を特定できること、でどうですか。
274
+
275
+ - chash.cpp - 拡張子 cpp は C++ ソースを意味するが、実際は C 。
276
+
277
+ ```C
278
+ void SHA512_transform(unsigned long long H[], unsigned long long W[])
279
+ {
280
+ static const unsigned long long K[80] = {
281
+ 0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL,
282
+ ```
283
+ K[] という配列名は #define K 128*2 と被ります。コードの可読性・保守性の観点から、両方の「K」という名前をそれぞれ変更したほうが良いと思います。
284
+
285
+ ・・・と、これだけでも、他の人が使えるコード品質になるには道のり長そうですね。
286
+ Enjoy !

1

回答を修正、コードレビューを追加

2020/02/29 15:13

投稿

rubato6809
rubato6809

スコア1382

answer CHANGED
@@ -1,7 +1,7 @@
1
- 多数のOP構造体変数がスタック領域に取られることから、**スタックオーバーフローが起こった**はないかと見ています。
1
+ 多数のOP構造体変数がスタック領域に取られることから、**スタックオーバーフローが起こった**ようです。
2
2
 
3
- OP型構造体変数は3072バイトのサイズはないかと。それがローカル変数としてスタック上にいくつもとられ、pattarson(), xgdb() 関数の引数にもなっています。他にもサイズの大きな変数が見つかります。スタックオーバーフローを起こしているとすれば、コード全体で変数の設計を考えなおす必要があると思います。
3
+ OP型構造体変数のサイズは3072バイトで。それがローカル変数としてスタック上にいくつもとられ、pattarson(), xgdb() 関数の引数にもなっています。他にもサイズの大きな変数が見つかります。コード全体で変数の設計を考えなおす必要があます。
4
- なお、-O2 最適化した場合は、ローカル変数の使用量を減らすような最適化がおこなわれるはないかと想像しています。
4
+ -O2 最適化した場合は、ローカル変数の使用量を減らす最適化がおこなわれるようです。
5
5
 
6
6
  oplib.c のみコンパイルして現象を再現できるようですが、コンパイルと起動方法、デバッグ操作が合ってるか、ご確認ください。ただしスタックオーバーフローは実行環境が異なれば、発生する場所が異なる可能性があると思います。以下は私の実行例です。
7
7
 
@@ -72,4 +72,131 @@
72
72
 
73
73
  ```
74
74
 
75
- コードレビューする側としてコメントしたいことはありますが、取り急ぎ問題点と思われる点を回答します。お気づきのように、既にコードに手を入れて、少しづつ私のスタイルに変更しつつあります。
75
+ コードレビューする側としてコメントしたいことはありますが、取り急ぎ問題点と思われる点を回答します。お気づきのように、既にコードに手を入れて、少しづつ私のスタイルに変更しつつあります。
76
+
77
+ ---
78
+ #スタックオーバーフローによってSegmentation Faultが起こった事は確実
79
+
80
+ > 使ってないOP構造体がたくさんありすぎてメモリが溢れた
81
+
82
+ その結果、
83
+ > -O2 最適化した場合は、ローカル変数の使用量を減らす最適化がおこなわれる
84
+
85
+ スタックオーバーフロー直前のスタックトップ付近のアドレスと、main() が動作開始した時点のスタックポインタ(付近)の差をとれば、どれだけスタック領域を消費したかがわかります。手元で調べたところ、約7.8MB消費したことがわかりました。xgcd() もスタックに巨大なローカル変数(v[K*2], u[K*2]等)を取りますので、それらを初期化しようとしたところで落ちたのでしょう。
86
+ 一方、-O2オプションでコンパイルすると未使用な変数は削除され、スタック領域の消費量が減り、3.8MB程度に半減したことがわかりました。
87
+
88
+ > 16Gもメモリを積んでいるので暴走しないはず
89
+
90
+ 各プロセスは実装メモリを独り占めすることはできません。OS(Linux, Windows等)はメモリ資源を管理しており、プロセスには実装メモリの一部分しか与えないからです。
91
+ [実行時スタックサイズ変更 on Linux](https://stakizawa.hatenablog.com/entry/20061017/t1)を見ると、デフォルトは8192であり、これは8MBがプロセスに与えられることを意味します。約7.8MB消費した時点でスタックは溢れる寸前だったわけです。このページに倣って、16MBのメモリを与えると最適化しないプログラムも動作を続けることができました。即ち、スタック領域が足りなかったことは確実です。
92
+ ```bash
93
+ $ ulimit -s 16384 <<= 16MB に拡張する
94
+ $ ./a.out <<= 動作できる
95
+ ```
96
+ ---
97
+ コードレビューしてみます。
98
+ - 不要な変数は削除しましょう。pattarson() 関数の中をざっと見ただけですが、
99
+ ```
100
+ unsigned short m[K],mm[T]={0},dd[K*D]={0}; // <<= すべて不要
101
+ OP h={0},r={0},aa[K]={0},tt={0},ff={0}; // aa[k] が不要
102
+ ```
103
+ OP aa[K]; だけで 786KB (== 3072 * 256)を消費します。こうした未使用の変数を削除するには**全ての警告(warning)を出させる**オプション -Wallを指定するとよいです。
104
+ $ cc oplib.c -Wall
105
+
106
+ - 不要な変数を、各関数に同じようにとっているかのようにも見えます。そこから、変数のスコープを理解していないのではないか、各関数の役割を整理しないままコーディングしているのではないか、といったことを疑います。
107
+ - コードが記述されたファイルをインクルードするのはイリーガル。インクルードするのはヘッダファイルにするのが普通。ヘッダファイルには定義を書き、コードは書かない。
108
+ ```
109
+ #include "chash.cpp" // インクルードしてはいけない。分割コンパイルせよ
110
+ #include "lu.c"
111
+ ```
112
+ 一番簡単な分割コンパイルのやり方は次(他にも修正が必要になりそうなので私は試してない)。
113
+ $ cc oplib.c chash.cpp lu.c
114
+
115
+ - 計算式で定義する定数マクロはカッコで囲むのが安全。数字をカッコで囲む人もいる。
116
+ ```
117
+ #define K (128*2) // こうしたマクロはカッコで守ろう
118
+ #define DEG (3*K)
119
+ #define T (K/2)
120
+ #define E (13)
121
+ #define D 6688 // こういう値の意味・根拠は?
122
+ ```
123
+ - K, T, E, D, c[2 * K + 1], g[K + 1] ・・・このように文字数の少ない識別子が大手を振って使われるのは、よくありません。スコープが狭い、或いは寿命が短い変数であれば構いませんけど。
124
+ - 構造体のメンバ名が短いのは、構わないけど、何か説明のコメントが欲しくなります。
125
+ ```
126
+ typedef struct {
127
+ unsigned short n; // 何かコメントを書こう
128
+ unsigned short a;
129
+ } oterm;
130
+ ```
131
+ - deg()関数はreturn値が不定となる可能性あり。-Wallは警告するかもしれない。
132
+ ```
133
+ int deg(vec a)
134
+ {
135
+ // 省略
136
+ if (n > 0)
137
+ return n;
138
+
139
+ // 警告!!!nが0なら戻り値は不定?
140
+ }
141
+ ```
142
+ if (n > 0) という条件判定は不要で、単に return n; すれば良いのではないか。
143
+
144
+ - 構造体を返す関数に注意。o2v(), v2o(), init_op()等は、ポインタを受け取って、そこに結果を書き込んだほうが効率が良いのだけどなあ・・・
145
+
146
+ - OP oadd(OP f, OP g) も、上記のようにポインタを受け取りそこに結果を書き込むこともできるが、今の仕様のままでもツッコミどころがいくつか。初期化が不要な変数だってある、初期化だけでも時間はかかる。
147
+ ```
148
+ OP oadd(OP f, OP g)
149
+ {
150
+ #ifdef ORIGIN
151
+ vec a = {0}, b = {0}, c = {0}; // a, b は初期化不要
152
+ #else
153
+ vec a, b, c = {0}; // c は0クリアしたほうが安全か
154
+ #endif
155
+ int i, k;
156
+ OP h = {0}; // 不要。下の return を見よ
157
+
158
+ a = o2v(f); // ここで a, b に値が代入される。0クリアする必要無し
159
+ b = o2v(g);
160
+
161
+ #ifdef ORIGIN // 元のコード
162
+ // 実行時、deg() を3回呼び出すが
163
+ if(deg(a) >= deg(b)){
164
+ k=deg(a)+1;
165
+ }else{
166
+ k=deg(b)+1;
167
+ }
168
+ #else          // 改良コード
169
+ int ka = deg(a); // 一時変数に取れば、呼出しは2回で済む
170
+ int kb = deg(b);
171
+ if (ka >= kb) {
172
+ k = ka + 1;
173
+ } else {
174
+ k = kb + 1;
175
+ }
176
+ #endif
177
+ // c を0で初期化しておく必要はあるか? 不明
178
+ for (i = 0; i < k; i++)
179
+ c.x[i] = a.x[i] ^ b.x[i]; // ここで c は x[k-1] まで決定する
180
+
181
+ #ifdef ORIGIN
182
+ h = v2o(c);
183
+ return h;
184
+ #else
185
+ return v2o(c); // 元の2行は1行で書けるので h は不要
186
+ #endif
187
+ }
188
+ ```
189
+ - ループの中で構造体変数 f の値は変化しないので、terms(f) は一回計算するだけで良い。
190
+ ```
191
+ int odeg(OP f)
192
+ {
193
+ int i, j = 0, k;
194
+ #ifdef ORIGIN
195
+ for (i = 0; i < terms(f) + 1; i++) { // terms(f) を毎回計算するの?
196
+ #else
197
+ k = terms(f) + 1; // 計算は一回だけ
198
+ for (i = 0; i < k; i++) {
199
+ #endif
200
+ ```
201
+ そもそも引数の f は、構造体そのものを引数にするのではなく、元の構造体へのポインタ(アドレス)だけ受け取れば十分なのではないか。構造体を引数にすると、実引数から仮引数へコピーが毎回発生するので効率悪い。
202
+ (もう寝るw)