5

は、次のプログラムを考えてみましょう。このプログラムの動作は明確に定義されていますか?この関数宣言の乱用は未定義の動作を呼び出しますか?</p> <pre><code>int main() { int exit(); ((void(*)())exit)(0); } </code></pre> <p>あなたが見ることができるように、<code>exit</code>が間違った戻り値の型で宣言されていますが、間違った関数型と呼ばれることはありません:

+0

gcc 4.7.2考える:exit.c:6:22:注意:このコードに達すると、プログラムは中止されます。だから私はgccの開発者がこれを未定義の振る舞いとみなしていると仮定しなければなりません。過去にgccの否定的な見方を表明してきたので、gccの意見を無視することもできます。 exit()の定義についても、他にもエラーがあります。 –

+0

実際、私の場合は、「エラー:機能するための引数が多すぎます」ということさえありますが、当然です。 –

+1

@Haroogan:CとC++は異なる言語です。 –

答えて

4

MSVCはこのプログラムに問題はありませんが、gccは(少なくともgcc 4.6.1以上で)問題ありません。

test.c: In function 'main': 
test.c:3:9: warning: conflicting types for built-in function 'exit' [enabled by default] 
test.c:4:22: warning: function called through a non-compatible type [enabled by default] 
test.c:4:22: note: if this code is reached, the program will abort 

そして、実行すると約束どおりにクラッシュします。クラッシュは誤った呼び出し規約や何かの事故ではありません。gccは、opcode 0x0b0fで明示的にクラッシュを強制する未定義命令を生成します(gdbは逆アセンブルします。ud2 - CPUマニュアルがオペコード):

main: 
.LFB0: 
    .cfi_startproc 
    push ebp 
    .cfi_def_cfa_offset 8 
    .cfi_offset 5, -8 
    mov ebp, esp 
    .cfi_def_cfa_register 5 
     .value 0x0b0f 
    .cfi_endproc 

私はそのコンパイラを書く人は、私よりもCについて多くの詳細を知っていると確信しているので、gccがこれを行うには間違っていると言うことには消極的です。しかし、ここで私は標準がそれについて言うことをどのように読んでいるのですか?表現で

A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the pointed-to type, the behavior is undefined.

、識別子:

C99は、関数ポインタの変換(6.3.2.3/8「ポインタ」)については、この意見:私は誰かが私には欠けているものを指摘すると確信していますexitは、関数ポインタを評価します。

サブ式((void(*)())exit)は、exitが評価する関数ポインタをタイプvoid (*)()の関数ポインタに変換します。次に、関数の呼び出しが標準ライブラリには、次のプロトタイプを持っているexitという名前の関数が含まれていint引数に0

を渡して、そのポインタを介して行われます。

void exit(int status); 

標準は、(7.1.4を言います/ 2「ライブラリ関数の使用」):

Provided that a library function can be declared without reference to any type defined in a header, it is also permissible to declare the function and use it without including its associated header.

あなたのプログラムは、そのプロトタイプを含むヘッダが含まれていませんが、変換されたポインタを介して行わ関数呼び出しはキャストで提供「宣言」を使用しています。キャスト内の宣言はプロトタイプ宣言ではないので、標準ライブラリで定義された関数タイプexitとプログラム内の変換された関数ポインタの関数タイプが互換性があるかどうかを判断する必要があります。戻り値の型が同じである(void)と - 標準は

For two function types to be compatible, both shall specify compatible return types. ... If one type has a parameter type list and the other type is specified by a function declarator that is not part of a function definition and that contains an empty identifier list, the parameter list shall not have an ellipsis terminator and the type of each parameter shall be compatible with the type that results from the application of the default argument promotions

変換された関数ポインタは、互換性のある関数型を持っているように私には思える(6.7.5.3/15「のプロトタイプを含む関数宣言子を()」)と言います既定の引数のプロモーションの後に、単一のパラメータの型はintになります。だから私には未定義の振る舞いはないように見えます。


更新:もう少しこれに思ったら、「自己宣言」ライブラリ関数名はと必ずしもそうではないが(正確に宣言されなければならないことを意味すると7.1.4/2を解釈するのが妥当かもしれませんプロトタイプですが、正しい戻り値の型があります)。特に、この規格では、「次のいずれかの項の外部リンクを持つすべての識別子は、常に外部リンケージを持つ識別子として使用するために予約されています」(7.1.3)。

私は、プログラムに未定義の振る舞いがあるという合理的な議論ができると思います。

+0

+1あなたのアップデートのために、私はそれが答えだと思います。 7.1.3には、 "7.1.4で許可されている以外の方法で予約されたコンテキスト内で識別子を宣言または定義するか、予約された識別子をマクロ名として定義した場合、その動作は未定義です"したがって、7.1.4がこれを可能にする議論ができない限り、コードは無効です。 – hvd

+0

ud2は、無効なオペコードの致命的なエラーを引き起こす「未定義命令」http://asm.inightmare.org/opcodelst/index.php?op=UD2です。 –

+0

OK、それは、その時点で意味を成していた単純化のために 'exit'を使用する私の選択のように、過剰な問題の原因かもしれません。別の翻訳単位で定義された関数 'void foo(int x)'が問題を変更した場合(新しい質問を開くか、最後に追加するか)、標準ライブラリ関数については疑問がありません、ちょうどタイプミスマッチ? –

0

私は、どちらの場合でもこの定義が間違っていると言います。

私の議論は、2つのコール((void(*)())exit)(0);exit();の生成コードが異なる可能性があるということです。したがって、int exit()が宣言されている場合(興味があるもの)、主な問題は、int exit(void)void exit(int)のバイナリレイアウトが必ずしも同じではないことです。

int exit()も定義されている場合、以下の理由によりクラッシュする可能性が最も高いでしょう。そこにはいくつかの呼び出し規約があります。たとえば、戻り値のスペースがスタックに予約されている場合など、問題が発生することがあります。したがって、((void(*)())exit)(0);が使用されている場合、関数自体(int exit())はそのことを知らないため、コンパイラによって(特に戻り値のために)スタック上の領域は予約されないため、依然としてintをプッシュしようとします実行時に予期されるメモリセル(予約されていてはならないはずのもの)に値を戻しますが、これは間違いなくクラッシュとして終了します。

+0

おそらく質問明確ではなかった。 'exit'の実際の型は' void exit(int) 'です。これは不変です。質問には、間違った型の 'exit'宣言が含まれていますが、間違った型で呼び出すことはありません。むしろ、正しい関数ポインタ型にアドレスをキャストして呼び出します。 –

+0

はい、私は理解していますが、バイナリレイアウトが 'int exit(void)'と 'void exit(int)'、または 'int exit(void)'の間で同じであることを濫用しようとしました。あなたが例えば 'void exit(void) 'のように宣言した場合に起こり得ることにさらに興味がありますか?言い換えれば、あなたは_ "間違った" _宣言がどれほど相容れないか気にしません。 –

0

これは動作を定義したと思います。標準の関連する部分のパラメータ(P6、ビット長い)についてと種類について、次のとおりです。この

If the function is defined with a type that is not compatible with the type (of the expression) pointed to by the expression that denotes the called function, the behavior is undefined.

すべてが常に約2異なるエンティティ、1つのに評価された関数式及び第2の機能を語りますそれは呼ばれます。式に盛り上がった識別子(あなたが誤って宣言したexit)は決してゲームには入りません。したがって、あなたの場合、関数は正しく呼び出され、UBはありません。

一般に、UBの場合、つまり関数ポインタを配列に格納するコード(たとえば、コンテキストに関するいくつかの追加知識に基づいて、キャストを使って関数を呼び出すコード)が壊れます。

ちょうど1つのニックピック、私はあなたがコンパイラに賛成し、そのような場合にプロトタイプを与える必要があると思います。引数の変換形式0は簡単で、この場合は正しいが、実際には非常にエラーが発生しやすい。

((void(*)(int))exit)(0); 

が良いでしょう。

更新:マイケルの答えを鑑みると、私は、ライブラリ以外の機能ですべてを行っていたら、上記のことが成り立っていることに同意します。しかし、7.1.3 p1では、ヘッダーで宣言されたプロトタイプと異なる識別子exitを使用することは禁止されています。 2州

If the program declares or defines an identifier in a context in which it is reserved (other than as allowed by 7.1.4), or defines a reserved identifier as a macro name, the behavior is undefined.

+0

これは非常に壊れやすいです。 stdlib.hを含む別のTUを持つか、またはexitを正しく宣言すると、動作は6.2.7p2によって未定義になります。 –

+0

@ JohannesSchaub-litb、これは標準ライブラリ関数であるとの事実を私はおそらく過小評価しました。私の更新を参照してください。 –

+0

@JensGustedt:はい、それが標準的なライブラリ関数であるという事実は問題になります。私は実際にはそうではない場合にもっと興味を持っていましたが、複数の翻訳単位を必要としないようにするために、「exit」で質問を書いていました。 –

関連する問題

 関連する問題