11

SO にはこの主題に関するリンクがたくさんありますが、何かが欠けていると思います:未指定の動作(UsB)、未定義の動作(UB)、および実装定義の動作(IDB ) の違いを平易な言葉で明確に説明します。 )ユースケースと例の詳細かつ簡単な説明。

注:この WIKI では簡潔にするためにUSBの頭字語を作成しましたが、他の場所で使用されるとは思わないでください。

これは他の投稿と重複しているように見えるかもしれませんが (より近いものはこれです)、誰かがこれを重複としてマークする前に、私が既に見つけたすべての資料の問題点を検討してください (そして、私は作成するつもりです)この投稿のコミュニティ WIKI):

  • 散らばった例が多すぎます。もちろん、例は悪くはありませんが、目前の問題にうまく適合する例を見つけることができない場合があるため、(特に初心者にとって) 混乱を招く可能性があります。

  • 多くの場合、例はほとんど説明のないコードのみです。このようなデリケートな問題については、特に (相対的な) 初心者にとっては、よりトップダウンのアプローチの方が優れている可能性があります。まず、明確で単純な説明と抽象的な (ただし、法律論的ではない) 説明、次に いくつかの簡単な例と、それらが何らかの動作を引き起こす理由の説明を示します。 .

  • 一部の投稿では、C と C++ の例が混在していることがよくあります。C と C++ は、UsB、UB、および IDB と見なされるものと一致しない場合があるため、両方の言語に精通していない人にとっては、例が誤解を招く可能性があります。

  • USB、UB、および IDB の定義が示されている場合、通常は標準の単純な引用であり、初心者にとっては不明確または難しすぎる場合があります。

  • 規格の引用が部分的な場合もあります。多くの投稿は、当面の問題に役立つ部分のみの標準を引用しています。これは良いことですが、一般性に欠けています。さらに、標準の引用には説明が伴わないことがよくあります (初心者には悪い)。

私自身はこのテーマの専門家ではないので、コミュニティ WIKI を作成して、興味のある人なら誰でも回答に貢献して改善できるようにします。

構造化された初心者向けの WIKIを作成するという私の目的を台無しにしないために、WIKI を編集する際にポスターがいくつかの簡単なガイドラインに従うことを望みます。

  • ユースケースを分類します。該当する場合は、既存のカテゴリの下にサンプル/コードを配置してみてください。そうでない場合は、新しいカテゴリを作成してください。

  • まずは平易な説明。最初に簡単な言葉で説明します (もちろん、単純化しすぎないでください - 品質が第一です!) 例またはあなたが言おうとしている点を説明してください。次に、コード サンプルまたは引用を入れます。

  • 基準を参照して引用します。さまざまな標準のスニペットを投稿しないでください。ただし、明確な参照 (例: C99 WG14/N... セクション 1.4.7、パラグラフ ...)提供し、可能であれば関連するリソースへのリンクを投稿してください。

  • 無料のオンライン リソースを優先します。書籍や自由に利用できないリソースを引用したい場合は問題ありませんが (WIKI の品質を向上させる可能性があります)、無料のリソースへのリンクもいくつか追加してみてください。これは、特に ISO 規格にとって非常に重要です。公式の標準へのリンクを追加することは大歓迎ですが、自由に入手できるドラフトへの同等のリンクも追加してみてください。また、ドラフトへのリンクを公式規格への参照に置き換えず、それらに追加してください。一部の大学の一部のコンピューター サイエンス学部でさえ、ISO 標準のコピーを持っていません。

  • 本当に必要でない限り、コードを投稿しないでください。平易な英語のみを使用した説明がぎこちない、または不明確な場合にのみ、コードを投稿してください。コード サンプルをワンライナーに限定するようにしてください。代わりに、他の SO Q&A へのリンクを投稿してください。

  • C++ の例を投稿しないでください。これをCの一種の FAQ にしたいと思います(ただし、誰かが C++ のツインスレッドを開始したいのであれば、それは素晴らしいことです)。C++ との関連する違いは歓迎されますが、補足としてのみです。つまり、C のケースを徹底的に説明した後、C プログラマーが C++ に切り替えるときに役立つのであれば、C++ に関するいくつかのステートメントを追加できます。通常、 「(この場合、C++ の動作は異なります)」のような簡単なメモと関連するリンクで十分です。

私は SO にかなり慣れていないので、この方法で Q&A を開始することで規則に違反していないことを願っています。この場合は申し訳ありません。モッズは私にそれについて知らせてくれることを歓迎します。

4

2 に答える 2

12

C 標準では、次のように要約できる方法で USB、UB、および IDB を定義しています。

未指定の動作 ( USB )

これは、実装が選択しなければならないいくつかの代替手段を標準が提供する動作ですが、選択をいつどのように行うかは規定していません。つまり、実装は、エラーを発生させずにその動作をトリガーするユーザー コードを受け入れる必要があり、標準によって指定された代替手段の 1 つに準拠する必要があります。

実装は、行われた選択について何も文書化する必要がないことに注意してください。これらの選択は、非決定論的であるか、(文書化されていない方法で) コンパイラ オプションに依存している場合もあります。

要約すると、標準は、その中から選択するいくつかの可能性を提供し、実装は、特定の代替案がいつ、どのように選択され、適用されるかを選択します。

標準は非常に多数の代替案を提供する場合があることに注意してください。典型的な例は、明示的に初期化されていないローカル変数の初期値です。標準では、変数のデータ型に対して有効な値である限り、この値は指定されていません。

より具体的には、int変数を考えてみましょう: 実装は任意のint値を自由に選択できます。この選択は、完全にランダムで、非決定論的であるか、実装の気まぐれに左右される可能性があり、それについて何も文書化する必要はありません。実装が標準で定められた制限内にとどまっている限り、これは問題なく、ユーザーは文句を言うことはできません。

未定義の動作 ( UB )

命名が示すように、これは、C 標準が、プログラムが何をするか、または何をすべきかを強制または保証しない状況です。すべての賭けはオフです。このような状況:

  • プログラムをエラーまたは非移植性のいずれかにする

  • 実装から絶対に何も必要としません

これは非常に厄介な状況です: 未定義の動作をするコードがある限り、プログラム全体がエラーと見なされ実装は標準によってすべてを行うことが許可されます

言い換えれば、UB の原因の存在は、UB をトリガーするプログラムが関係している限り、実装が標準を完全に無視することを可能にします。

この場合の実際の動作は無限の可能性をカバーする可能性があることに注意してください。以下は決して完全なリストではありません。

  • コンパイル時エラーが発行される場合があります。
  • 実行時エラーが発生する場合があります。
  • 問題は完全に無視されます (これはプログラムのバグにつながる可能性があります)。
  • コンパイラは、最適化として UB コードを静かに破棄します。
  • ハードディスクがフォーマットされている可能性があります。
  • あなたのコンピュータはあなたの銀行口座を消去し、あなたのガールフレンドにデートを求めるかもしれません.

最後の 2 つの (半分シリアスな) 項目で、UB の厄介さについて正しい直感が得られることを願っています。また、ほとんどの実装では、ハード ドライブをフォーマットするために必要なコードが挿入されませんが、実際のコンパイラは最適化を行います。

用語に関する注意:実装/システム/環境で標準が UB のソースと見なすコードの一部が文書化された方法で動作するため、実際には UB ではない、と主張する人がいます。この推論は間違っていますが、よくある (そしてある程度理解できる) 誤解です。C のコンテキストでUB (および UsB と IDB) という用語が使用される場合、それは正確な意味を持つ技術用語を意味し意味は規格によって定義されています。特に、「未定義」という言葉は日常的な意味を失います。したがって、誤ったプログラムや移植性のないプログラムが「明確に定義された」動作を反例として生成する例を示すことは意味がありません。あなたがしようとすると、あなたは本当に要点を逃します。UB は、標準のすべての保証を失うことを意味します。実装が拡張機能を提供する場合、保証は実装の保証のみです。その拡張機能を使用すると、そのプログラムは準拠する C プログラムではなくなります (ある意味では、標準に準拠していないため、C プログラムではなくなります!)。

未定義の動作の有用性

UB に関するよくある質問は、次のようなものです。

まず、最適化。実装が UB の考えられる原因をチェックしないようにすることで、C プログラムを非常に効率的にする多くの最適化が可能になります。これは C の特徴の 1 つですが、C は初心者にとって多くの落とし穴の元になります。

第 2 に、標準に UB が存在することで、全体として不適合と見なされることなく、適合する実装が C に拡張を提供できるようになります。

特定のプラットフォームで役立つ可能性のある非標準の機能を提供する場合がありますが、実装が適合プログラムに義務付けられているように動作する限り、それ自体は適合しています。もちろん、これらの機能を使用するプログラムは移植性がなく、文書化された UBに依存します。つまり、標準では UB の動作ですが、実装では拡張として文書化されます。

実装定義の動作 ( IDB )

これは、USB と同様の方法で説明できる動作です。標準ではいくつかの代替案が提供され、実装では 1 つが選択されますが、実装では、選択がどのように行われるかを正確に文書化する必要があります

これは、コンパイラのドキュメントを読むユーザーに、特定のケースで何が起こるかを正確に予測するのに十分な情報を提供する必要があることを意味します。

IDB を完全に文書化していない実装は、準拠しているとは見なされないことに注意してください。準拠する実装では、標準で IDB が宣言されている場合に何が起こるかを正確に文書化する必要があります。



不特定の動作の例

評価の順序

関数の引数

関数の引数の評価順序は指定されていません EXP30-C

たとえば、 inc(a(), b());では、関数aが の前または後に呼び出されるかどうかが指定されていませんb。唯一の保証は、両方がc関数の前に呼び出されることです。



未定義の動作の例

ポインター

ヌルポインタの逆参照

Null ポインターは、ポインターが有効なメモリを指していないことを通知するために使用されます。そのため、null ポインターを介してメモリの読み取りまたは書き込みを試みることはあまり意味がありません。

技術的には、これは未定義の動作です。ただし、これはバグの非常に一般的な原因であるため、ほとんどの C 環境では、null ポインターを逆参照しようとすると、プログラムがすぐにクラッシュします (通常はセグメンテーション違反で強制終了されます)。このガードは、配列や構造体への参照に関連するポインター演算が原因で完全ではないため、最新のツールを使用しても、null ポインターを逆参照するとハード ドライブがフォーマットされる可能性があります。

初期化されていないポインタの逆参照

null ポインターと同様に、値を明示的に設定する前にポインターを逆参照することは UB です。NULL ポインタとは異なり、ほとんどの環境では、コンパイラが警告できることを除いて、この種のエラーに対するセーフティ ネットは提供されません。とにかくコードをコンパイルすると、UB の不快感を経験する可能性が高くなります。

無効なポインターの逆参照

無効なポインターは、割り当てられたメモリ領域内にないアドレスを含むポインターです。無効なポインターを作成する一般的な方法は、 を呼び出すfree()(呼び出しの後、ポインターは無効になります。これが呼び出しのほとんどのポイントですfree()) か、ポインター演算を使用して、割り当てられたメモリ ブロックの制限を超えるアドレスを取得します。

これは、UB を逆参照するポインタの最も悪い変形です。セーフティ ネットはなく、コンパイラの警告もありません。コードが何かを実行する可能性があるという事実だけがあります。ほとんどのマルウェア攻撃は、プログラムでこの種の UB の動作を使用して、プログラムが動作するようにプログラムを動作させます (トロイの木馬、キーロガーのインストール、ハード ドライブの暗号化など)。この種の UB では、フォーマットされたハード ドライブの可能性が非常に現実的になります。

一貫性を捨てる

オブジェクトを として宣言するとconst、そのオブジェクトの値を決して変更しないことをコンパイラーに約束します多くの場合、コンパイラはそのような無効な変更を見つけて、私たちに怒鳴りつけます。しかし、このスニペットのように constness をキャストすると、次のようになります。

int const a = 42;
...
int* ap0 = &a;      //< error, compiler will tell us
int* ap1 = (int*)&a; //< silences the compiler
...
*ap1 = 43;          //< UB ==> program crash?

コンパイラはこの無効なアクセスを追跡できず、コードを実行可能ファイルにコンパイルできず、実行時にのみ無効なアクセスが検出され、プログラムがクラッシュする可能性があります。

カテゴリー2

ここにタイトルを入れてください!

ここにあなたの説明を入れてください!



実装定義の動作の例

カテゴリー1

ここにタイトルを入れてください!

ここにあなたの説明を入れてください!

于 2013-08-24T16:38:33.797 に答える
1

N1570は、ISO C 標準のドラフトであり、公式の ISO ドキュメントに非常に近いものです。

N1256は初期のドラフトであり、C99 標準に加えて 3 つの技術正誤表からの変更が組み込まれています。

附属書 J には 5 つのセクションがあり、それぞれが標準の残りの部分に散らばっている情報を収集します。

  • J.1 規定されていない振る舞い
  • J.2 未定義の動作
  • J.3 実装定義の動作
  • J.4 ロケール固有の動作
  • J.5 共通拡張
于 2013-08-31T21:14:20.103 に答える