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

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Q&A

解決済

7回答

1943閲覧

戻り値未指定時の挙動

退会済みユーザー

退会済みユーザー

総合スコア0

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

0グッド

0クリップ

投稿2018/03/26 13:45

0

0

コンパイラのverは不明なのですが、下記のようなコードがあった場合、
以前は0が返ってきていたのに、リコンパイル後、不定値(0でも1でも2でも3でもない値)が返ってくる。
という事象が発生しました。原因など分かる方がいましたら教えていただけないでしょうか。

int sample() {
if ( xxx ) {
return 1;
}
if ( yyy ) {
return 2;
}
if ( zzz ) {
return 3;
}
}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答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
raccy

総合スコア21784

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

maisumakun

2018/03/27 01:56

前に別な回答でコメントしましたように、あくまで「使用されている」場合なので、}に到達して抜ける段階までは合法、ということにはなりますが、実用上気にすべきものではないですね(関数がどう呼ばれるかなんて、関数を書く側で気にするものではないですし)。
guest

0

原因など分かる方がいましたら教えていただけないでしょうか。

intなど、void以外の型を宣言した関数で値を返さずに末尾の}に達した場合、その返り値を使うと未定義の動作となります。つまり、そういうコードを書いてしまった場合、何が起きるかは(C言語の規格上)何も保証されません。あるコンパイラは一貫して特定の値を返すかも知れませんし、別な処理系では状況によって返り値が違うかも知れません。クラッシュしてしまっても、他のメモリを破壊しながら動作を続けても、文句は言えません。

特定のコンパイラのドキュメント化された動作に依存するのでもなければ、このようなコードは書かないというのが、いちばん妥当な選択肢です。

投稿2018/03/26 13:52

maisumakun

総合スコア146702

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ベストアンサー

質問に対する解答ではありませんが、豆知識として。

呼び出し規約:cdeclというルールがあります。
関数呼び出しの戻り値はx86ではEAX、x64ではRAXレジスタに入れて返すというルールがあります。
そのため、関数を抜ける直前で代入処理などをしていてEAX, RAXが使われていた場合は「偶然」その値が返ります。

参考
https://msdn.microsoft.com/ja-jp/library/7572ztz4.aspx

投稿2018/03/27 01:53

nullbot

総合スコア910

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

return 0がどこにも書いてないのに0が帰ってきてたとしたらそれは「たまたま」だというのは他の方が回答しているとおりです。
コンパイラのバージョンアップとかコンパイルオプション(最適化レベルとかデバッグ)変更など、様々な要因で値をreturnしない関数の戻り値は変わります。

不定値が返るようになった回のコンパイルとひとつ前のコンパイルとで何か状況が変わっているのでは?
ちょっと前に話題になったインテルCPUのセキュリティホール対策でのバージョンアップとか。

投稿2018/03/27 05:59

a_saitoh

総合スコア702

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

返値を与えていない場合、2つの可能性があります。最近のコンパイラでは、値を返さないとエラーになるのでは?

  1. 値を返さない。返された値が入る部分の代入は実際には起こらない。例の場合、
    int i = sample() ;とあれば、iへの代入が正しく起こらない。
    関数が終了するときに、関数の中で使われたメモリが開放され、返値があれば、その領域が確保されて、そこに値が設定される。なければ、領域は省略され、直ちに呼び出し側に制御が移る。
  2. 関数sampleの中で値が設定されない状態で値が返る。
    関数の開始時に、返値用の領域が予約されます。予約された時点では、0にクリアとかはしない(コンパイラによってはするかもしれない)。その前に使われていたゴミの値が入っている。関数終了時には、その予約された領域の値が返る。

投稿2018/03/27 01:34

gm300

総合スコア580

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

asm

2018/03/27 01:54

https://wandbox.org/permlink/USP13QTrD7k2z1rk 最新のgccにてコンパイル出来ることを確認できました。 > iへの代入が正しく起こらない。 "正しく"の定義次第な部分はありますが、私の知る限り(intel/arm/mips)のCPUにおいて 通常の呼び出し規約では代入自体は正しく行われていると思いますが > 返値があれば、その領域が確保されて 返り値の有無を判別できるとは、どこの呼び出し規約なのでしょうか?
gm300

2018/03/27 05:12

「正しく」の部分は、スタックフレームを使って呼び出し/戻り制御を行っている場合、下位の関数では、返値がないものとしてスタックフレームを開放すると、失敗するような気がします。ああ、そんな実装は行わない気もしてきました。スタックフレームのサイズは、32+返値のバイト数で、returnするときは、その値だけSPを増やす みたいな実装があれば駄目。return するときは、pc = mem[sp], sp=mem[sp-8] .. みたいな実装であれば(1)の心配は無い。 cでは文法違反になりそうですが、struct { int a[100000] ;} s0 ;struct {int a[10] ;} s1 でreturn s0 ;としてs1 = (s1)func()すると変になる気もします。 返値があるかないかは、(1)関数の宣言でわかる。(2)returnする値でわかる。void funcとあれば返値は、存在せず、double d ;return d とあればdoubleの値が返りそうです。勿論そのままでは文法エラーです。 (2)のケースでreturn された値だけを見て、returnが実際に行われたかどうは判断できない気がします。って質問ですか? 関数の型宣言が無いpythonのような場合は、実行してみないとわかりません。
guest

0

そのプログラムで言えば、xxxもyyyもzzzも0であったなら、どのreturnもスキップしちゃいますから、返す値を決めているものがない、ということになります。

コンパイルし直しただけでそういう変化があったとすると、xxx,yyy,zzzがローカル変数として宣言されていて、かつ初期値が与えられないで不定値となっているので以前はたまたまどれかが0以外になっていたのが今回は全部が0になる条件になったのではないか、というのがとりあえず疑われるところでしょうか。

投稿2018/03/26 14:50

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

そりゃ返り値がないからです
なにがかえってきても文句言えません

投稿2018/03/26 13:46

y_waiwai

総合スコア88180

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.30%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問