未定義の動作(この例ではゼロ除算)を呼び出すコードは実行されませんが、プログラムはまだ未定義の動作ですか?
int main(void)
{
int i;
if(0)
{
i = 1/0;
}
return 0;
}
それはまだ未定義の振る舞いだと思いますが、私を支持または否定するための証拠を標準で見つけることができません。
それで、何かアイデアはありますか?
未定義の動作(この例ではゼロ除算)を呼び出すコードは実行されませんが、プログラムはまだ未定義の動作ですか?
int main(void)
{
int i;
if(0)
{
i = 1/0;
}
return 0;
}
それはまだ未定義の振る舞いだと思いますが、私を支持または否定するための証拠を標準で見つけることができません。
それで、何かアイデアはありますか?
回答:
C標準が「動作」および「未定義の動作」という用語をどのように定義しているかを見てみましょう。
参照は、ISO C2011標準のN1570ドラフトです。公開されている3つのISOC標準(1990、1999、および2011)のいずれにも関連する違いはありません。
セクション3.4:
行動
外観または行動
わかりました、それは少し曖昧ですが、実際に実行されない限り、特定のステートメントには「外観」がなく、確かに「アクション」がないことを主張します。
セクション3.4.3:
この国際規格が要件を課していない、移植不可能または誤ったプログラム構成または誤ったデータの使用時の未定義の動作動作
そのような構成の「使用時に」と書かれています。「使用する」という言葉は規格で定義されていないため、一般的な英語の意味に戻ります。コンストラクトは、実行されない場合は「使用」されません。
その定義の下にメモがあります:
注考えられる未定義の動作は、予測できない結果で状況を完全に無視することから、環境に特徴的な文書化された方法で翻訳またはプログラムの実行中に動作すること(診断メッセージの発行の有無にかかわらず)、翻訳または実行の終了(診断メッセージの発行)。
したがって、コンパイラは、動作が定義されていない場合、コンパイル時にプログラムを拒否することが許可されます。しかし、それについての私の解釈は、プログラムのすべての実行が未定義の動作に遭遇することを証明できる場合にのみそうすることができるということです。これは、私が思うに、これは:
if (rand() % 2 == 0) {
i = i / 0;
}
確かにこれはでき未定義の動作を持って、コンパイル時に拒否することはできません。
実際問題として、プログラムは、未定義の動作の呼び出しを防ぐためにランタイムテストを実行できる必要があり、標準ではそうすることを許可する必要があります。
あなたの例は:
if (0) {
i = 1/0;
}
これは、0による除算を実行することはありません。非常に一般的なイディオムは次のとおりです。
int x, y;
/* set values for x and y */
if (y != 0) {
x = x / y;
}
の場合y == 0
、除算の動作は確かに未定義ですが、の場合は実行されませんy == 0
。動作は明確に定義されており、例が明確に定義されているのと同じ理由で、潜在的な未定義の動作が実際に発生することはありません。
(INT_MIN < -INT_MAX && x == INT_MIN && y == -1
(はい、整数除算がオーバーフローする可能性がある)場合を除きますが、それは別の問題です。)
コメント(削除されたため)で、コンパイラがコンパイル時に定数式を評価する可能性があると誰かが指摘しました。これは真実ですが、この場合は関係ありません。
i = 1/0;
1/0
定数式ではありません。
定数式は、に帰着構文カテゴリである条件式(除外割り当てとコンマ式)。プロダクション定数式は、大文字と小文字のラベルなど、実際に定数式を必要とするコンテキストでのみ文法に表示されます。だからあなたが書くなら:
switch (...) {
case 1/0:
...
}
次に1/0
、定数式であり、6.6p4の制約に違反する式です。「各定数式は、そのタイプの表現可能な値の範囲内の定数に評価される必要があります。」したがって、診断が必要です。ただし、代入の右側には定数式は必要なく、条件式のみが必要であるため、定数式の制約は適用されません。コンパイラーは、コンパイル時に実行できる任意の式を評価できますが、動作が実行中に評価された場合と同じである場合(または、コンテキストではif (0)
、execution()中に評価されない場合に限ります。
(定数式とまったく同じように見えるものは、必ずしも定数式である必要はありません。ちょうどx + y * z
、シーケンスx + y
が表示されるコンテキストのために、シーケンスが加法式ではないのと同じです。)
これは、私が引用しようとしていたN1570セクション6.6の脚注を意味します。
したがって、次の初期化で
static int i = 2 || 1 / 0;
は、式は値1の有効な整数定数式です。
実際にはこの質問には関係ありません。
最後に、実行中に何が起こるかではなく、未定義の動作を引き起こすように定義されていることがいくつかあります。付録J、C標準のセクション2(ここでも、N1570ドラフトを参照)には、標準の残りの部分から収集された、未定義の動作を引き起こすものがリストされています。いくつかの例(これが完全なリストであるとは言いません)は次のとおりです。
- 空でないソースファイルは、バックスラッシュ文字が直前にない改行文字で終了しないか、部分的な前処理トークンまたはコメントで終了します
- トークンの連結により、ユニバーサル文字名の構文に一致する文字シーケンスが生成されます
- 基本ソース文字セットにない文字は、識別子、文字定数、文字列リテラル、ヘッダー名、コメント、またはトークンに変換されない前処理トークンを除いて、ソースファイルで検出されます。
- 識別子、コメント、文字列リテラル、文字定数、またはヘッダー名に無効なマルチバイト文字が含まれているか、初期シフト状態で開始および終了しない
- 同じ識別子には、同じ変換ユニット内に内部リンケージと外部リンケージの両方があります
これらの特定のケースは、コンパイラーが検出できるものです。委員会がすべての実装に同じ動作を課すことを望まなかった、またはできなかったため、それらの動作は定義されていないと思います。許可された動作の範囲を定義することは、努力する価値がありませんでした。これらは実際には「実行されないコード」のカテゴリには分類されませんが、完全を期すためにここで言及します。
1/0
定数式であることから除外されませんか?
1/0
定数式ではありません質問の文脈では、それは次のように解析されていないので、定数式、単にとして、条件付き表現の一部だ代入表現。case 1/0:
制約に違反し、診断が必要になります。
この記事では、セクション2.6でこの質問について説明します。
int main(void){
guard();
5 / 0;
}
著者は、プログラムがguard()
終了しないときに定義されると考えています。また、「静的に未定義」と「動的に未定義」の概念を区別していることに気づきます。例:
標準11の背後にある意図は、一般に、状況のコードを生成するのが容易でない場合、状況は静的に未定義になるということのようです。コードを生成できる場合にのみ、状況を動的に定義できません。
11)委員との私的な通信。
記事全体を見ることをお勧めします。一緒に取られて、それは一貫した絵を描きます。
記事の著者が委員会のメンバーと質問について話し合う必要があったという事実は、基準が現在あなたの質問への回答について曖昧であることを裏付けています。
guard()
未定義ではないと仮定)、ステートメント5 / 0;
が実際に実行された場合に限り、動作は未定義です。(コンパイラーは、の評価を5 / 0
への呼び出しなどに合法的に置き換えることができることに注意してくださいabort()
。プログラムは、実行がそのポイントに達した場合にのみ中止されます。)コンパイラーは、guard()
常に終了すると判断できる場合にのみ、そのプログラムを拒否できます。
guard()
終了しないと判断する高度なコンパイラは、5/0のコードをまったく生成する必要はありません。対照的に、のコードを生成する方法はありません。それも正しくない(int)(void)5
ため(int)(void)z
、のコードを生成することはできません。したがって、著者は次のように考えています…
if (0) (int)(void)5;
、素朴なコンパイラーに提示する難問のためにプログラムを拒否することができますが、のような到達不能な動的UBif (0) 5 / 0;
は無害です。これは委員会のメンバーとの彼らの議論から生じたものであり、私は他の場所で同様の議論がなされているのを見ました(しかし、おそらく同じ情報源から、特にそれがどこにあったか覚えていないので)。私は現在C99の理論的根拠を検討していますが、これについての言及があれば、戻ってきて指摘します。
(int)(void)5
制約違反です。N1570 6.5.4、キャスト演算子の説明:「制約:型名がvoid型を指定しない限り、型名はアトミック、修飾、または非修飾スカラー型を指定し、オペランドはスカラー型でなければなりません。」(void)5
スカラー型を持たないため、それ(int)(void)5
を含むコードが実行されたかどうかに関係なく、その制約に違反します。
この場合、未定義の動作はコードの実行の結果です。したがって、コードが実行されない場合、未定義の動作はありません。
未定義の動作がコードの宣言のみの結果である場合(たとえば、変数シャドウイングのケースが未定義の場合)、実行されていないコードは未定義の動作を呼び出す可能性があります。
#include "//e"
どちらがUBを呼び出すかを検討します。
私はこの答えの最後の段落に行きます:https://stackoverflow.com/a/18384176/694576
... UBは実行時の問題であり、コンパイル時の問題ではありません...
したがって、いいえ、呼び出されるUBはありません。
未定義の振る舞いに関しては、形式的な側面と実際的な側面を区別するのは難しいことがよくあります。これは、1989年の標準における未定義の動作の定義です(最新バージョンは手元にありませんが、これが大幅に変更されるとは思われません)。
1つの未定義の動作 移植性のない、または誤ったプログラム構成の使用時、または この国際規格が要件を課していない誤ったデータ 2注考えられる未定義の動作は、状況を完全に無視することから始まります。 予測できない結果が発生し、翻訳またはプログラムの実行中に動作する 環境に特徴的な文書化された方法で( 診断メッセージの発行)、翻訳の終了または 実行(診断メッセージの発行を伴う)。
正式な観点からは、プログラムは未定義の動作を呼び出すと思います。つまり、ゼロによる除算が含まれているという理由だけで、標準は実行時に何をするかについて何の要件も課していません。
一方、実用的な観点からは、直感的に期待どおりに動作しないコンパイラを見つけて驚いたでしょう。
基準によれば、私が正しく覚えているように、ルールが破られた瞬間から何でもできるようになっています。ある種のグローバルな味わいのある特別なイベントがあるかもしれません(しかし、私はそのようなことについて聞いたり読んだりしたことはありません)...だから私は言うでしょう:行動が明確に定義されている限り、0は常にfalseなので、実行時にルールを破ることはできません。
それはまだ未定義の振る舞いだと思いますが、私を支持または否定するための証拠を標準で見つけることができません。
プログラムは未定義の振る舞いを呼び出さないと思います。
欠陥レポート#109は同様の質問に対処し、次のように述べています。
さらに、特定のプログラムを実行するたびに未定義の動作が発生する場合、特定のプログラムは厳密には準拠していません。準拠する実装は、厳密に準拠するプログラムの変換に失敗してはなりません。そのプログラムを実行すると、未定義の動作が発生する可能性があるからです。fooが呼び出されることはない可能性があるため、指定された例は、準拠する実装によって正常に変換される必要があります。
これは、「未定義の振る舞い」という表現がどのように定義されているか、およびステートメントの「未定義の振る舞い」がプログラムの「未定義の振る舞い」と同じであるかどうかによって異なります。
このプログラムはCのように見えるので、コンパイラーが使用するC標準(一部の回答が行ったように)をより深く分析することが適切です。
指定された基準がない場合、正解は「状況によって異なります」です。一部の言語では、最初のエラーの後のコンパイラーは、コンパイラーの推測によると、プログラマーが何を意味するのかを推測し、それでもコードを生成しようとします。他のより純粋な言語では、何かが未定義になると、その未定義はプログラム全体に伝播します。
他の言語には「制限付きエラー」の概念があります。一部の限られた種類のエラーの場合、これらの言語は、エラーが発生する可能性のある損傷の程度を定義します。特に、ガベージコレクションが暗黙的に含まれている言語では、エラーによって入力システムが無効になるかどうかが異なることがよくあります。