12

このメッセージの意味を知っていますが、なぜエラーメッセージではなく、単なる警告なのか疑問に思いました。

この場合はどうなりますか?たとえば、私が関数を持っているとしましょう

int f()
{
}

そして、私がそれを呼ぶとどうなりますか?
この場合、コンパイラは「初期化されていない」の戻りを追加しますintか?
または、リターンが欠落していると、スタックが破損する可能性がありますか?
それとも(絶対に)未定義の動作ですか?

gcc4.1.2および4.4.3でテスト済み


編集:答えを読む私は一つのことを理解し、コメントを読む-別の..

OK、要約しましょう:それは未定義の振る舞いです。つまり、スタックが破損する可能性があるということですよね?(それは、私のコンピューターがマイクジャックを通して腐ったトマトを私の上に投げ始め、「あなたは何をしましたか?」と叫び始めるかもしれないことを意味します)。

しかし、もしそうなら、なぜここでの一番の答えは、スタックの破損は起こり得ないと同時に、振る舞いは未定義であると言っているのでしょうか?

そして、に関して未定義?「戻り値ではない」を使用しようとする呼び出し元、または値を返す必要がある場合は関数の終わりだけが未定義ですが、そうではありませんか?

または、それは未定義の動作ではなく、値を使用しようとするユーザー(返されません、d'oh!)だけが未定義の値を「受信」しますか?言い換えれば、ごみの価値だけで、それ以上何も起こり得ないのでしょうか?

4

7 に答える 7

11

A:いいえ、戻り値が欠落していてもスタックが破損することはありません

A:はい。呼び出し元が(未定義!)戻り値を読み取ったり使用したりしようとした場合、動作は「未定義」になります。

PS:

C++の引用は次のとおりです。

C++03§6.6.3/2:

関数の終わりから流れ出るのは、値のない戻りと同じです。これにより、値を返す関数で未定義の動作が発生します。

于 2012-09-11T19:20:40.883 に答える
7

あなたは C と C++ の両方について尋ねました。ルールは 2 つの言語で異なります。

C では、呼び出し元が関数によって返された値を使用しようとした場合のみ、動作は未定義です。あなたが持っている場合:

int func(void) {
    /* no return statement */
}

...

func();

次に、動作が明確に定義されます。

C++ では、呼び出し元が結果を使用しようとするかどうかにかかわらず、動作は未定義です (関数が呼び出された場合)。(これは歴史的な理由によるものです。ANSI より前の C にはvoidキーワードがなく、値を返すことを意図していない関数は一般に (暗黙的に) return と定義されていましたint。)

John Bode の回答では、2011 ISO C 標準6.9.1p12 のN1570 ドラフトが既に引用されています。

関数を終了する}に到達し、関数呼び出しの値が呼び出し元によって使用された場合、動作は未定義です。

そして、paulsm4 は C++ 標準を引用しました。最新の 2011 年バージョン 6.6.3p2 を引用します。

関数の最後から流れることは、return値のない a と同じです。これにより、値を返す関数で未定義の動作が発生します。

値が呼び出し元によって使用されない限り、値を返す関数が値を返さないことを C が許可するようになった歴史的な理由は、C++ には当てはまりません。古い (ANSI C より前の) コードを壊します。

C (C99 以降) と C++ の両方で、main関数は特殊なケースです。}return を実行せずに の終了に到達するmainことは、 a と同等return 0;です。main(C は、 以外の実装定義型を返すことを許可しintます。その (まれな) ケースでは、最後から外れると、ホスト環境に不特定の終了ステータスが返されます。)

もちろんreturn、値を返す関数からステートメントを省略したり、ステートメントに到達しない可能性のある実行パスを持つことreturnは、悪い考えです。、および inintの代用として使用する古代のレガシー C コードでのみ意味があります (ただし、 の場合でも、個人的には明示的な を使用するのが好きです)。voidmainmainreturn 0;

于 2013-05-15T18:00:57.963 に答える
4

標準では、それは未定義と見なされます。

実際には、戻り値用に予約されているメモリまたはレジスタが読み取られます。そこにあるものは何でもあります。

于 2012-09-11T19:23:53.823 に答える
4

C 2011 ドラフト N1570

6.9.1 関数定義

...
12}関数を終了する に到達し、関数呼び出しの値が呼び出し元によって使用された場合、動作は未定義です。

「未定義」とは、コンパイラが特定の方法でこの状況を処理することを言語標準で要求されていないことを意味します。どのアクションも「正しい」と見なされます。コンパイラは、診断を発行して変換を停止するか、診断と完全な変換を発行するか (これが表示されます)、問題を完全に無視するかを自由に選択できます。

実際の実行時の動作に関しては、次のようなものに依存します。

  • 呼び出し元は戻り値をどのように使用していますか?
  • 使用されている呼び出し規約は何ですか?
  • 基礎となるアーキテクチャはどのように動作しますか?

于 2012-09-11T20:05:13.387 に答える
2

場合によっては難しいため、コンパイラはこれを診断する必要はありません。したがって、ルールは動作が未定義であるということです。

于 2012-09-11T19:22:37.713 に答える
0

g ++でテストを行いました。ランダムなものを含むオブジェクトを取得したようです。つまり、コンストラクターを呼び出しません。整数のみを扱っている場合、それは乱数を取得することを意味します。文字列を処理すると、セグメンテーション違反が発生します。それが汚職の意味かどうかはわかりませんが、悪いことです。

最初の質問については、さらに詳しく説明します。これがデフォルトで無効になっている警告である理由を知りたいです! 私にはかなり重要なようです。

#include <string>
#include <iostream>

class X
{
private:
  int _x;
public:
  X(int x) : _x(x) { } // No default construcor!                            
  int get() const { return _x; }
};

X test1(int x)
{
  if (x > 0)
    return X(x);
  // warning: control reaches end of non-void function                      
}

class Y
{
private:
  std::string _y;
public:
  Y(std::string y) : _y(y) { } // No default construcor!                    
  std::string get() const { return _y; }
};

Y test2(std::string y)
{
  if (y.length() > 3)
    return Y(y);
  // warning: control reaches end of non-void function                      
}

int main(int, char**)
{
  std::cout<<"4 -> "<<test1(4).get()<<std::endl;
  std::cout<<"-4 -> "<<test1(-4).get()<<std::endl;
  std::cout<<"stop -> "<<test2("stop").get()<<std::endl;
  std::cout<<"go -> "<<test2("go").get()<<std::endl;
}
于 2013-12-30T18:57:04.320 に答える
0

Linux 64ビット、GCC 4.63で簡単なテストを実施しました.GCCがそのようなものをどのようにアセンブルするかを実際に見てみましょう..

簡単な例を作成しました

これは通常の戻り値を持つ test.c です

int main()
{
    return 0;
}

これは、test.c の GCC アセンブラー出力です。

main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    $0, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc

これは、戻り値のない test2.c です。

int main()
{
}

これは、test2.c の GCC アセンブラー出力です。

main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc

基本的に、次の行が欠落していることがわかります。

    movl    $0, %eax

この行はeax、関数の戻り値であるレジスタに値を移動します。関数が実際の状況で使用された場合、おそらくガベージ値を含むレジスタは...eaxの戻り値を表しますmain()

于 2013-05-15T19:40:15.630 に答える