コンパイラのverは不明なのですが、下記のようなコードがあった場合、
以前は0が返ってきていたのに、リコンパイル後、不定値(0でも1でも2でも3でもない値)が返ってくる。
という事象が発生しました。原因など分かる方がいましたら教えていただけないでしょうか。
int sample() {
if ( xxx ) {
return 1;
}
if ( yyy ) {
return 2;
}
if ( zzz ) {
return 3;
}
}
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

回答7件
0
質問のコードで全てのif
文が偽であった場合、つまり、返り値の型がvoid
では無いのに、return
によって返り値を指定しない場合の動作は未定義です。
C11仕様書最終ドラフト n1570 §6.9.1 Function definitons p.157
12 If the
}
that terminates a function is reached, and the value of the function call is used by the caller, the behavior is undefined.
12 関数を終了する}
に達し、関数呼び出しの値が呼び出し元によって使用されている場合、その動作は未定義です。
Cでの未定義の動作というのは、コンパイラの種類、バージョン、コンパイル時オプション、実行時の環境、実行時の状態によって、どのような動作になるのかはわからないことを意味します。ですので、何が起きても不思議ではなく、Cの仕様通りの動作になります。
なお、main
のみ例外で、暗黙的に最後にreturn 0
がある場合と同等の動作になります。(§5.1.2.2.3 Program termination 1 p.14)
【追記】
maisumakunさんがコメントで指摘していることについての追記です。
さて、n1570やmaisumakunさんが書いて有るとおり、未定義になるのは関数の返り値が使われたときです。
C
1int x = sample();
とあったときに、x
がどうなるのかはわかりませんが、
C
1sample();
とだけあったときは、返り値を使わないことに意味があるのかどうかさておき、特に問題はありません。しかし、関数の返り値が使われるか、使われないかは関数側では制限できないため、実質使われる物であると判断しなければなりません。どうしても使われないということにしたいのであれば、関数に対するドキュメントとして「条件が○○で、かつ、その返り値を使用した場合の動作は未定義である。」と書いておく必要があります(Cでは、標準ライブラリの関数を含め、条件付きで未定義な動作をする関数は珍しくありません)。
※ 返り値の型がvoid
である場合は、返り値を使わないように強制できるため、問題は起きません。
このように、呼び出し側に依存するが、未定義にもならない場合もあると言うことあり、GCCでは-Wall
を付けている場合、警告が表示されます。
さて、上とは異なり、呼び出し側がどのような場合であっても、次の場合は動作が未定義とはなりません。
C
1int sample() { 2if ( xxx ) { 3return 1; 4} 5if ( yyy ) { 6return 2; 7} 8if ( zzz ) { 9return 3; 10} 11 exit(0); 12}
なぜなら、関数が終了する前にプログラムの終了処理が走って、関数には戻らないため、関数が終了して返り値が使われるということが起こりえないからです。exit()
以外でもlongjump()
などの戻らない関数でも同じことが言えます。GCCではこのような場合には警告を出さず、正常にコンパイルできます。
投稿2018/03/26 14:22
編集2018/03/27 13:27総合スコア21784
0
原因など分かる方がいましたら教えていただけないでしょうか。
int
など、void
以外の型を宣言した関数で値を返さずに末尾の}
に達した場合、その返り値を使うと未定義の動作となります。つまり、そういうコードを書いてしまった場合、何が起きるかは(C言語の規格上)何も保証されません。あるコンパイラは一貫して特定の値を返すかも知れませんし、別な処理系では状況によって返り値が違うかも知れません。クラッシュしてしまっても、他のメモリを破壊しながら動作を続けても、文句は言えません。
特定のコンパイラのドキュメント化された動作に依存するのでもなければ、このようなコードは書かないというのが、いちばん妥当な選択肢です。
投稿2018/03/26 13:52
総合スコア146702
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
ベストアンサー
質問に対する解答ではありませんが、豆知識として。
呼び出し規約:cdeclというルールがあります。
関数呼び出しの戻り値はx86ではEAX、x64ではRAXレジスタに入れて返すというルールがあります。
そのため、関数を抜ける直前で代入処理などをしていてEAX, RAXが使われていた場合は「偶然」その値が返ります。
投稿2018/03/27 01:53
総合スコア910
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
return 0がどこにも書いてないのに0が帰ってきてたとしたらそれは「たまたま」だというのは他の方が回答しているとおりです。
コンパイラのバージョンアップとかコンパイルオプション(最適化レベルとかデバッグ)変更など、様々な要因で値をreturnしない関数の戻り値は変わります。
不定値が返るようになった回のコンパイルとひとつ前のコンパイルとで何か状況が変わっているのでは?
ちょっと前に話題になったインテルCPUのセキュリティホール対策でのバージョンアップとか。
投稿2018/03/27 05:59
総合スコア702
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
返値を与えていない場合、2つの可能性があります。最近のコンパイラでは、値を返さないとエラーになるのでは?
- 値を返さない。返された値が入る部分の代入は実際には起こらない。例の場合、
int i = sample() ;とあれば、iへの代入が正しく起こらない。
関数が終了するときに、関数の中で使われたメモリが開放され、返値があれば、その領域が確保されて、そこに値が設定される。なければ、領域は省略され、直ちに呼び出し側に制御が移る。 - 関数sampleの中で値が設定されない状態で値が返る。
関数の開始時に、返値用の領域が予約されます。予約された時点では、0にクリアとかはしない(コンパイラによってはするかもしれない)。その前に使われていたゴミの値が入っている。関数終了時には、その予約された領域の値が返る。
投稿2018/03/27 01:34
総合スコア580
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

0
そのプログラムで言えば、xxxもyyyもzzzも0であったなら、どのreturnもスキップしちゃいますから、返す値を決めているものがない、ということになります。
コンパイルし直しただけでそういう変化があったとすると、xxx,yyy,zzzがローカル変数として宣言されていて、かつ初期値が与えられないで不定値となっているので以前はたまたまどれかが0以外になっていたのが今回は全部が0になる条件になったのではないか、というのがとりあえず疑われるところでしょうか。
投稿2018/03/26 14:50

退会済みユーザー
総合スコア0
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/03/27 01:56