10

C++ で未定義の動作が何を意味するのかを誰かが正確に明確にしてくれることを期待していました。次のクラス定義があるとします。

class Foo
{
public:
    explicit Foo(int Value): m_Int(Value) { }
    void SetValue(int Value) { m_Int = Value; }

private:
    Foo(const Foo& rhs);
    const Foo& operator=(const Foo& rhs);

private:
    int m_Int;
};

私が正しく理解している場合、次のコードの参照とポインターの両方への 2 つの const_casts は、型 Foo の元のオブジェクトの const-ness を削除しますが、ポインターまたは参照のいずれかを介してこのオブジェクトを変更しようとすると、未定義の動作が発生します。

int main()
{
    const Foo MyConstFoo(0);
    Foo& rFoo = const_cast<Foo&>(MyConstFoo);
    Foo* pFoo = const_cast<Foo*>(&MyConstFoo);

    //MyConstFoo.SetValue(1);   //Error as MyConstFoo is const
    rFoo.SetValue(2);           //Undefined behaviour
    pFoo->SetValue(3);          //Undefined behaviour

    return 0;
}

私を困惑させているのは、これが機能しているように見え、元の const オブジェクトを変更するのに、この動作が未定義であることを通知する警告が表示されない理由です。大まかに言えば、const_casts が嫌われていることは知っていますが、C スタイルのキャストによって const_cast が作成される可能性があるという認識の欠如が、気付かれずに発生する可能性があることを想像できます。たとえば、次のようになります。

Foo& rAnotherFoo = (Foo&)MyConstFoo;
Foo* pAnotherFoo = (Foo*)&MyConstFoo;

rAnotherFoo->SetValue(4);
pAnotherFoo->SetValue(5);

この動作によって致命的な実行時エラーが発生する可能性があるのは、どのような状況ですか? この (潜在的に) 危険な動作を警告するために設定できるコンパイラ設定はありますか?

注意: MSVC2008 を使用しています。

4

7 に答える 7

11

C++ で未定義の動作が何を意味するのかを誰かが正確に明確にしてくれることを期待していました。

技術的には、「未定義の動作」とは、言語がそのようなことを行うためのセマンティクスを定義していないことを意味します。

実際には、これは通常、「やらないでください。コンパイラが最適化を実行するとき、またはその他の理由で壊れる可能性があります」という意味です。

私を困惑させているのは、これが機能しているように見え、元の const オブジェクトを変更するのに、この動作が未定義であることを通知する警告が表示されない理由です。

この特定の例では、変更不可能なオブジェクトを変更しようとすると、「機能しているように見える」か、プログラムに属していないメモリまたは他のオブジェクトの [一部] に属しているメモリが上書きされる可能性があります。オブジェクトはコンパイル時に最適化されているか、メモリ内の読み取り専用データ セグメントに存在している可能性があります。

これらの事態を引き起こす可能性のある要因は、複雑すぎて列挙できません。初期化されていないポインター (UB も) を逆参照する場合を考えてみましょう。そのとき操作している「オブジェクト」には、ポインターの場所でたまたまメモリ内にあった値に依存する任意のメモリ アドレスがあります。その「値」は、以前のプログラムの呼び出し、同じプログラムでの以前の作業、ユーザーが提供した入力のストレージなどに潜在的に依存しています。未定義の動作を呼び出した場合に考えられる結果を合理化しようとすることは単に実行不可能であるため、通常はそうしません。気にせず、代わりに「やらないで」と言ってください。

私を困惑させているのは、これが機能しているように見え、元の const オブジェクトを変更するのに、この動作が未定義であることを通知する警告が表示されない理由です。

さらに複雑なことに、コンパイラは未定義の動作を診断 (警告/エラーを発行) する必要はありません。これは、未定義の動作を呼び出すコードは、形式が正しくない (つまり、明らかに違法である) コードと同じではないためです。多くの場合、コンパイラが UB を検出することさえ困難であるため、これはコードを適切に記述することがプログラマーの責任となる領域です。

型システム (constキーワードの存在とセマンティクスを含む) は、破損するコードの記述に対する基本的な保護を提供します。constC++ プログラマーは、このシステムを破壊すること (たとえば、ネスをハッキングすることなど) は自己責任で行われ、通常は悪い考えで あることを常に認識しておく必要があります。™</p>

C型キャストが成り立つという認識不足が、いつの間にか発生しているケースも想像const_castできます。

絶対。警告レベルが十分に高く設定されている場合、健全なコンパイラこれについて警告することを選択する場合がありますが、そうする必要はありませんし、そうでない場合もあります。一般に、これは C スタイルのキャストが嫌われている正当な理由ですが、C との後方互換性のために依然としてサポートされています。これは不幸なことの 1 つにすぎません。

于 2011-09-08T14:32:29.237 に答える
4

未定義の動作は、オブジェクトが生まれた方法によって異なります。Stephan が00:10:00頃に説明しているのを見ることができますが、基本的には以下のコードに従ってください。

void f(int const &arg)
{
    int &danger( const_cast<int&>(arg); 
    danger = 23; // When is this UB?
}

現在、呼び出しには2つのケースがありますf

int K(1);
f(k); // OK
const int AK(1); 
f(AK); // triggers undefined behaviour

まとめると、K生まれつきは非 const なので、f を呼び出すときのキャストは問題ないのに対し、AK生まれつきはconstso ... UB です。

于 2014-03-08T16:55:05.577 に答える
4

未定義の動作とは文字通り、言語標準によって定義されていない動作を意味します。これは通常、コードで何か問題が発生しているにもかかわらず、コンパイラがエラーを検出できない場合に発生します。エラーをキャッチする唯一の方法は、実行時テストを導入することです。これはパフォーマンスを低下させます。その代わりに、言語仕様は、特定のことをしてはならないことを示しています。

const_castコンパイル時のチェックを無効にするために定数オブジェクトに書き込む場合、次の 3 つのシナリオが考えられます。

  • 非定数オブジェクトのように扱われ、書き込みによって変更されます。
  • 書き込み保護されたメモリに配置され、書き込みによって保護違反が発生します。
  • コンパイルされたコードに埋め込まれた定数値によって(最適化中に)置き換えられるため、書き込み後も初期値のままです。

テストでは、最初のシナリオになりました。オブジェクトは (ほぼ確実に) 書き込み保護されていないスタック上に作成されました。オブジェクトが静的な場合は 2 番目のシナリオが発生し、さらに最適化を有効にすると 3 番目のシナリオが発生することがあります。

一般に、コンパイラはこのエラーを診断できません。参照またはポインタのターゲットが定数であるかどうかを判断する方法はありません (あなたのような非常に単純な例を除いて)。安全であることを保証できる場合にのみ使用することを確認するのはあなた次第ですconst_cast-オブジェクトが定数ではない場合、または実際に変更するつもりがない場合。

于 2011-09-08T14:39:06.727 に答える
4

私を困惑させているのは、これが機能しているように見える理由です

それが未定義の動作の意味です。
動作するように見えるなど、何でもできます。
最適化レベルを最高値まで上げると、おそらく機能しなくなります。

しかし、この動作が未定義であることを通知する警告も表示されません。

変更を行った時点で、オブジェクトはconstではありません。一般に、オブジェクトが元々 const であったとは判断できないため、警告することはできません。それがあったとしても、各ステートメントは他のステートメントを参照せずに独自に評価されます(そのような警告生成を見る場合)。

第二に、キャストを使用することにより、コンパイラに「自分が何をしているのかを知っているので、すべての安全機能をオーバーライドしてそれを実行してください」と伝えます。

たとえば、次のようにすると問題なく動作します:

float aFloat;

int& anIntRef = (int&)aFloat;  // I know what I am doing ignore the fact that this is sensable
int* anIntPtr = (int*)&aFloat;

anIntRef  = 12;
*anIntPtr = 13;

私は const_casts が大まかに言えば嫌われていることを知っています

それは彼らを見る間違った方法です。これらは、賢明な人々によって検証される必要がある何か奇妙なことをコードで文書化する方法です (コンパイラーは間違いなくキャストに従います)。検証に賢い人が必要な理由は、未定義の動作につながる可能性があるためですが、コードでこれを明示的に文書化したのは良いことです (そして、人々は間違いなくあなたが行ったことを注意深く見ます)。

しかし、C スタイルのキャストによって const_cast が作成される可能性があるという認識の欠如が、気付かれずに発生する可能性がある場合を想像できます。たとえば、次のようになります。

C++ では、C スタイルのキャストを使用する必要はありません。
最悪の場合、C スタイルのキャストは reinterpret_cast<> に置き換えることができますが、コードを移植するときは、static_cast<> を使用できたかどうかを確認する必要があります。C++ キャストのポイントは、それらを目立たせることです。これにより、それらを確認して、危険なキャストと無害なキャストの違いが一目でわかります。

于 2011-09-08T14:59:02.753 に答える
2

古典的な例は、保護されたデータ セグメントに存在する可能性のある const 文字列リテラルを変更しようとすることです。

于 2011-09-08T14:24:48.590 に答える
0

コンパイラは、最適化のためにメモリの読み取り専用部分に const データを配置する場合があり、このデータを変更しようとすると UB が発生します。

于 2011-09-08T14:28:05.487 に答える
0

static および const データは、ローカル変数以外のプログラムの別の部分に格納されることがよくあります。const 変数の場合、これらの領域は多くの場合、変数の constness を強制するために読み取り専用モードになります。読み取り専用メモリに書き込もうとすると、オペレーティング システムによって反応が異なるため、「未定義の動作」が発生します。「未定義の動作」とは、言語がこのケースの処理方法を指定していないことを意味します。

メモリについてより詳細な説明が必要な場合は、こちらをお読みになることをお勧めします。UNIXでの説明ですが、どのOSでも同様の仕組みが使われています。

于 2011-09-08T14:35:08.523 に答える