前提・実現したいこと
いま暗号を実装しています。だいたい出来上がったのでGitHubにソースコードを載せているのですが、
コードレビューの方法がわかりません。
コードを第3社に見てもらうためにはどうすればいいのでしょうか?
開発言語はLinuxのC言語です。
またプログラミングする上で、気をつけなければならない基本的なことがあったら教えてください。
コードに関する質問でないですが、この質問は不適切でしょうか?
よろしくお願いします。
GitHubのアドレスはここです。
https://github.com/anang0g0/GoppaDecorder
この中のoplib.cがメインの関数です。コメントの付け方とか、基本的なコードの書き方とかでもいいです。
このコードは、以下のサイトと関係しています。方式はMcEliece暗号です。
https://csrc.nist.gov/projects/post-quantum-cryptography/round-2-submissions
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
退会済みユーザー
2020/02/25 17:40
2020/02/25 17:46
退会済みユーザー
2020/02/25 17:58 編集
2020/02/25 19:00
退会済みユーザー
2020/02/25 19:15
退会済みユーザー
2020/02/25 19:51 編集
退会済みユーザー
2020/02/25 21:12
2020/02/26 00:06
2020/02/26 00:32
退会済みユーザー
2020/02/26 01:24 編集
2020/02/26 01:47
回答3件
0
ベストアンサー
以下、可読性重視でコメントしますね。
- 先ず1ソースが長すぎます、分割しましょう。
- defineのマクロ名はせめて単語レベルで記述した方が良いと思います。(変数名も同様)
- 外部変数が多いのも気になります。getter/setter関数を作成して隠蔽化をお勧めします。
- 四則演算は演算子の優先順位に頼らずカッコで順序を明示しましょう。
例)99行目
while(count<K/2-1){
↓
while(count < ((k / 2) - 1)) {
※正直コードそのものが暗号化しちゃっていて追うのが辛いです。。。
問題のクラッシュですが、具体的にはどんなエラーとなるのでしょうか?
そこそこ大きなサイズのローカル変数があるようなのでstack overflowとか?
投稿2020/02/26 00:24
総合スコア1095
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
退会済みユーザー
2020/02/26 01:03
2020/02/26 01:44
退会済みユーザー
2020/02/26 03:44
0
・インデントが出来ていません.
・{} の付け方, スペースの入れ方, 空行の入れ方が一貫していません.
これだけでコードレビューはNG判定され, 次のレビュー段階には進めなかったりします.
IDE を使っていれば即直せるのですから, やっていないことを言われますし, 使っていなければ, 修正に時間がかかることですから最初からやれと言われます.
投稿2020/02/26 15:04
総合スコア13209
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
退会済みユーザー
2020/02/26 22:09 編集
2020/02/26 23:25
退会済みユーザー
2020/02/26 23:40
退会済みユーザー
2020/02/26 23:47 編集
2020/02/27 01:03
退会済みユーザー
2020/02/27 01:24
2020/02/27 01:28
2020/02/27 01:34
退会済みユーザー
2020/02/27 01:46 編集
退会済みユーザー
2020/02/27 02:12 編集
2020/02/27 02:14
2020/02/27 02:29 編集
2020/02/27 02:35
退会済みユーザー
2020/02/27 02:43 編集
退会済みユーザー
2020/03/01 11:48
0
多数のOP構造体変数がスタック領域に取られることから、スタックオーバーフローが起こったようです。
OP型構造体変数のサイズは3072バイトです。それがローカル変数としてスタック上にいくつもとられ、pattarson(), xgdb() 関数の引数にもなっています。他にもサイズの大きな変数が見つかります。コード全体で変数の設計を考えなおす必要があります。
-O2 最適化した場合は、ローカル変数の使用量を減らす最適化がおこなわれるようです。
oplib.c のみコンパイルして現象を再現できるようですが、コンパイルと起動方法、デバッグ操作が合ってるか、ご確認ください。ただしスタックオーバーフローは実行環境が異なれば、発生する場所が異なる可能性があると思います。以下は私の実行例です。
bash
1$ cc oplib.c -g 2oplib.c: In function ‘add’: 3oplib.c:404:9: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘long long unsigned int’ [-Wformat=] 4 printf("n1=%d\n",n1); <<= 無視して構わない警告が数件、省略 5 6$ gdb ./a.out 7# gdb の開始メッセージが10数行、省略 8Reading symbols from ./a.out...done. 9(gdb) r <<= 実行開始 10 11# 膨大な表示、省略 12963x^20+2839x^19+7684x^18+7501x^17+4141x^16+4568x^15+7852x^14+532x^13+326x^12+3373x^11+6021x^10+5577x^9+2960x^8+660x^7+7390x^6+2625x^5+1835x^4+4297x^3+3995x^2+4073x^1+6692 g1!========= 13DEBUG: point 1. <<= 追加したデバッグ表示 14 15Program received signal SIGSEGV, Segmentation fault. 160x00000000004086e6 in xgcd (f=..., g=...) at oplib.c:1013 17warning: Source file is more recent than executable. 181013 { 19(gdb) l 201008 // exit(1); 211009 return h; 221010 } 231011 241012 EX xgcd(OP f,OP g) 251013 { 261014 printf("DEBUG: f: %p, g: %p\n", &f, &g); 271015 281016 OP h={0},ww={0},v[K*2]={0},u[K*2]={0}; 291017 oterm a,b; 30(gdb) bt 31#0 0x00000000004086e6 in xgcd (f=..., g=...) at oplib.c:1013 32#1 0x000000000040c570 in pattarson (w=..., f=...) at oplib.c:1626 33#2 0x000000000040efdb in main () at oplib.c:2098 34(gdb) 35(gdb) p f 36$1 = {t = {{n = 0, a = 1}, {n = 1, a = 1}, {n = 0, a = 0}, {n = 3, a = 1}, { 37 n = 4, a = 1}, {n = 0, a = 0}, {n = 0, a = 0}, {n = 0, a = 0}, {n = 8, 38# OP f 変数のサイズが大きいため表示行多し、省略 39 n = 101, a = 1}, {n = 0, a = 0}, {n = 0, a = 0}, {n = 104, a = 1}, { 40---Type <return> to continue, or q <return> to quit---
main() => pattarson() => xgcd() と関数呼出しが行われ、xgcd() 関数の先頭でSegmentation Fault を起こしたように見えます。私は xgcd() 関数の先頭にも printf() でDEBUG表示を追加しましたが、その表示はありませんので、 xgcd() 関数内部の問題ではなさそうです。
デバッグのため、pattarson() 関数の中にも printf() 行を追加してあります。
C
1void pattarson(OP w,OP f) 2{ 3 OP g1={0},ll={0},s={0}; 4 int i,j,k,l,c; 5 /// 省略 6 printf(" g1!=========\n"); 7 printf("DEBUG: point 1.\n"); // ここを通過直後 8 9 if(LT(g1).n==0 && LT(g1).a==0){ 10 printf("DEBUG: point 2.\n"); // ここは通らない 11 12 printpol(o2v(w)); 13 printf(" badkey=========\n"); 14 exit(1); 15 } 16 hh = xgcd(w, g1); // ここで SIGSEGV? 17
コードレビューする側としてコメントしたいことはありますが、取り急ぎ問題点と思われる点を回答します。お気づきのように、既にコードに手を入れて、少しづつ私のスタイルに変更しつつあります。
#スタックオーバーフローによってSegmentation Faultが起こった事は確実
使ってないOP構造体がたくさんありすぎてメモリが溢れた
その結果、
-O2 最適化した場合は、ローカル変数の使用量を減らす最適化がおこなわれる
スタックオーバーフロー直前のスタックトップ付近のアドレスと、main() が動作開始した時点のスタックポインタ(付近)の差をとれば、どれだけスタック領域を消費したかがわかります。手元で調べたところ、約7.8MB消費したことがわかりました。xgcd() もスタックに巨大なローカル変数(v[K2], u[K2]等)を取りますので、それらを初期化しようとしたところで落ちたのでしょう。
一方、-O2オプションでコンパイルすると未使用な変数は削除され、スタック領域の消費量が減り、3.8MB程度に半減したことがわかりました。
16Gもメモリを積んでいるので暴走しないはず
各プロセスは実装メモリを独り占めすることはできません。OS(Linux, Windows等)はメモリ資源を管理しており、プロセスには実装メモリの一部分しか与えないからです。
実行時スタックサイズ変更 on Linuxを見ると、デフォルトは8192であり、これは8MBがプロセスに与えられることを意味します。約7.8MB消費した時点でスタックは溢れる寸前だったわけです。このページに倣って、16MBのメモリを与えると最適化しないプログラムも動作を続けることができました。即ち、スタック領域が足りなかったことは確実です。
bash
1$ ulimit -s 16384 <<= 16MB に拡張する 2$ ./a.out <<= 動作できる
コードレビューしてみます。
- 不要な変数は削除しましょう。pattarson() 関数の中をざっと見ただけですが、
unsigned short m[K],mm[T]={0},dd[K*D]={0}; // <<= すべて不要 OP h={0},r={0},aa[K]={0},tt={0},ff={0}; // aa[k] が不要
OP aa[K]; だけで 786KB (== 3072 * 256)を消費します。こうした未使用の変数を削除するには全ての警告(warning)を出させるオプション -Wallを指定するとよいです。
$ cc oplib.c -Wall
- 不要な変数を、各関数に同じようにとっているかのようにも見えます。そこから、変数のスコープを理解していないのではないか、各関数の役割を整理しないままコーディングしているのではないか、といったことを疑います。
- コードが記述されたファイルをインクルードするのはイリーガル。インクルードするのはヘッダファイルにするのが普通。ヘッダファイルには定義を書き、コードは書かない。
#include "chash.cpp" // インクルードしてはいけない。分割コンパイルせよ #include "lu.c"
一番簡単な分割コンパイルのやり方は次(他にも修正が必要になりそうなので私は試してない)。
$ cc oplib.c chash.cpp lu.c
- 計算式で定義する定数マクロはカッコで囲むのが安全。数字をカッコで囲む人もいる。
#define K (128*2) // こうしたマクロはカッコで守ろう #define DEG (3*K) #define T (K/2) #define E (13) #define D 6688 // こういう値の意味・根拠は?
- K, T, E, D, c[2 * K + 1], g[K + 1] ・・・このように文字数の少ない識別子が大手を振って使われるのは、よくありません。スコープが狭い、或いは寿命が短い変数であれば構いませんけど。
- 構造体のメンバ名が短いのは、構わないけど、何か説明のコメントが欲しくなります。
typedef struct { unsigned short n; // 何かコメントを書こう unsigned short a; } oterm;
- deg()関数はreturn値が不定となる可能性あり。-Wallは警告するかもしれない。
int deg(vec a) { // 省略 if (n > 0) return n; // 警告!!!nが0なら戻り値は不定? }
if (n > 0) という条件判定は不要で、単に return n; すれば良いのではないか。
-
構造体を返す関数に注意。o2v(), v2o(), init_op()等は、ポインタを受け取って、そこに結果を書き込んだほうが効率が良いのだけどなあ・・・
-
OP oadd(OP f, OP g) も、上記のようにポインタを受け取りそこに結果を書き込むこともできるが、今の仕様のままでもツッコミどころがいくつか。初期化が不要な変数だってある、初期化だけでも時間はかかる。
OP oadd(OP f, OP g) { #ifdef ORIGIN vec a = {0}, b = {0}, c = {0}; // a, b は初期化不要 #else vec a, b, c = {0}; // c は0クリアしたほうが安全か #endif int i, k; OP h = {0}; // 不要。下の return を見よ a = o2v(f); // ここで a, b に値が代入される。0クリアする必要無し b = o2v(g); #ifdef ORIGIN // 元のコード // 実行時、deg() を3回呼び出すが if(deg(a) >= deg(b)){ k=deg(a)+1; }else{ k=deg(b)+1; } #else // 改良コード int ka = deg(a); // 一時変数に取れば、呼出しは2回で済む int kb = deg(b); if (ka >= kb) { k = ka + 1; } else { k = kb + 1; } #endif // c を0で初期化しておく必要はあるか? 不明 for (i = 0; i < k; i++) c.x[i] = a.x[i] ^ b.x[i]; // ここで c は x[k-1] まで決定する #ifdef ORIGIN h = v2o(c); return h; #else return v2o(c); // 元の2行は1行で書けるので h は不要 #endif }
- ループの中で構造体変数 f の値は変化しないので、terms(f) は一回計算するだけで良い。
int odeg(OP f) { int i, j = 0, k; #ifdef ORIGIN for (i = 0; i < terms(f) + 1; i++) { // terms(f) を毎回計算するの? #else k = terms(f) + 1; // 計算は一回だけ for (i = 0; i < k; i++) { #endif
そもそも引数の f は、構造体そのものを引数にするのではなく、元の構造体へのポインタ(アドレス)だけ受け取れば十分なのではないか。構造体を引数にすると、実引数から仮引数へコピーが毎回発生するので効率悪い。
(もう寝るw)
さらに追加します。
-
実際、不要なローカル変数が山のよう・・・
-
関数の最後にあるべき return 文が無く、関数の最後で目的の値を返さない経路がある。既に、上で int deg(vec a) 関数を指摘したが、他にもあった。コード品質の低さを示すもの。
unsigned short oinv(unsigned short a) // BUG !!!
unsigned char chk(OP f)
int isqrt(unsigned short u)
OP osqrt(OP f, OP w) --- 長大な関数
- 信用を失う滅茶苦茶な関数w
C
1OP ToHorner(OP f) 2{ 3 vec v = o2v(f); // この計算は何? 4 OP h; 5 return h; // 不定値を返す OMG 6}
- 変数に代入するが使わない。何をするつもりか意図が不明な処理が多数見つかる。
C
1// 計算結果を使わない例1 2int main(void) // argc, argv を使わない 3{ 4 unsigned short mm[T]; // mm[t] = {0} 初期化不要 5 // 途中省略 6 for (i = 0; i < T; i++) { 7 mm[i] = r.t[i].a; // mm[i] に代入するも、使わない 8 9// 計算結果を使わない例2 10OP ogcd(OP f, OP g) // ユークリッドの互除法? 11{ 12 OP h, ww; // 初期化不要 13 14 for (int i = 0; i < T; i++) { 15 h = omod(f, g); 16 ww = odiv(f, g); // この除算は不要 17 f = g; 18 g = h; 19 } 20// 一方、xgcd() は除算した結果 ww を使う。そこをコピペしたか?
lu.c を拝見しました。
- 一文字のグローバル変数が!言語道断モノですw
C
1int i, j, k; // カウンタ !!! OMG !!! 2int n = F; // 配列の次数 ??? for fwrite()
ここをコメントアウトすると、これらに頼っているコードが判明します。
この小文字変数 k に頼るコードが det() 関数に見つかります。
C
1void det(unsigned short g[]) 2{ 3 // 省略 4 k = cc[K]; 5 // 途中省略 6 cc[K] = k;
この大文字「K」は #define K 128*2 という定数マクロです。大文字・小文字のK, kを一緒に使うのは、いかがなものかと(乱視が悪化してきた私は)思う。
i, j, k は、頼っている関数にローカル変数を追加すれば削除できます。
問題は n です。n の主目的は fwrite(dd, 1, n, fq); の引数とみられますが、他の箇所から n の値を変更可能です。たとえば oplib.c に scanf("%d", &n); が何箇所かあり、(意図通りかどうかは不明だが)キー入力した値がこの n に読み込まれるらしい。グローバル変数に対する典型的な戒めを変数「n」に見出すことができます。
しかも「n」という変数名は、関数内のローカル変数にもあるうえに、oterm 構造体のメンバ変数でもある。カオスです。
グローバル変数、マクロ名、寿命の長い変数、アルゴリズム的・意味的に重要な変数などはもっと長い変数名にしたほうが良いでしょう。ひとつの目安は grep コマンドで、それぞれの変数が登場する箇所を特定できること、でどうですか。
- chash.cpp - 拡張子 cpp は C++ ソースを意味するが、実際は C 。
C
1void SHA512_transform(unsigned long long H[], unsigned long long W[]) 2{ 3 static const unsigned long long K[80] = { 4 0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL,
K[] という配列名は #define K 128*2 と被ります。コードの可読性・保守性の観点から、両方の「K」という名前をそれぞれ変更したほうが良いと思います。
・・・と、これだけでも、他の人が使えるコード品質になるには道のり長そうですね。
Enjoy !
scanfを入れているのは、表示を確認するために一時停止をしたいから
そのためにグローバル変数に読み込むのは本末転倒・言語道断。その後 fwrite() はどうなるか考えよ。
scanf() で停止するなら <stdio.h> の入力関数 fgets() 等でも停まります。getchar(); とだけ書いたこともあるけど、普通は小さな関数を作って、停めたい所で呼ぶでしょう。
C
1// extern int n; ← NG! 現状、こうなっている 2void wait4keyinput(void) 3{ 4 int n; // 読み込む変数はローカルに取るべし 5 printf(" (hit return) "); // 何か表示させたほうが良いだろう 6 fflush(stdout); // just in case 7 scanf("%d", &n); // fgets(line, LINESIZE, stdin); という手も 8}
投稿2020/02/26 06:00
編集2020/03/01 22:59総合スコア1382
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/02/26 06:13 編集
退会済みユーザー
2020/02/26 09:03 編集
退会済みユーザー
2020/02/27 03:10
2020/02/27 03:52
退会済みユーザー
2020/02/27 04:55 編集
退会済みユーザー
2020/02/27 04:51 編集
退会済みユーザー
2020/02/27 04:51 編集
退会済みユーザー
2020/02/29 16:42
退会済みユーザー
2020/03/01 01:09
2020/03/01 01:40
退会済みユーザー
2020/03/01 02:22 編集
退会済みユーザー
2020/03/01 11:59 編集
2020/03/01 23:21 編集
退会済みユーザー
2020/03/01 23:46
退会済みユーザー
2020/03/03 07:44
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。