const_castによる未定義の動作
-
27-10-2019 - |
質問
私は、誰かが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_castを正しく理解した場合、タイプFOOの元のオブジェクトのconst-nes-fooのconst-nest-firterを介してこのオブジェクトを変更する試みは行われました。未定義の動作につながります。
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);
この動作はどのような状況で致命的なランタイムエラーを引き起こす可能性がありますか?この(潜在的に)危険な行動を警告するために設定できるコンパイラ設定はありますか?
NB:MSVC2008を使用しています。
解決
私は、誰かがC ++の未定義の行動が意味することを正確に明確にすることができることを望んでいました。
技術的には、「未定義の行動」とは、言語がそのようなことをするためのセマンティクスを定義しないことを意味します。
実際には、これは通常それをしないでください;コンパイラが最適化を実行するとき、または他の理由で壊れる可能性があります。」
私を困惑させているのは これが機能するように見える理由と元のconstオブジェクトを変更する理由 しかし、この動作が未定義であることを私に通知するよう警告しても私に促しません。
この具体的な例では、マット不能なオブジェクトを変更しようとすると、「動作しているように見える」か、プログラムに属さないメモリまたは他のオブジェクトに属するメモリを上書きする可能性があります。オブジェクトはコンパイル時に最適化されている可能性があるか、メモリ内のいくつかの読み取り専用データセグメントに存在する可能性があります。
これらのことにつながる可能性のある要因は、単に複雑すぎてリストできません。非信頼化されたポインター(UB)を控除する場合を考慮してください。次に、「オブジェクト」には、ポインターの場所でメモリにある値があったものに依存する任意のメモリアドレスがあります。その「価値」は、以前のプログラムの呼び出し、同じプログラムでの以前の作業、ユーザーが提供する入力のストレージなどに潜在的に依存しています。未定義の動作を呼び出す可能性のある結果を合理化しようとすることは単に実行不可能です。 tを気にし、代わりに言う」それをしないでください".
私を困惑させているのは、これが機能しているように見える理由であり、元のconstオブジェクトを変更する理由です しかし、この動作が未定義であることを私に通知するよう警告しても私に促しません。
さらなる複雑さとして、コンパイラはそうではありません 必要 未定義の動作を呼び出すコードは、不正なコードと同じではないため、未定義の動作のために診断(警告/エラーを発する)診断(つまり、明示的に違法)。多くの場合、コンパイラがUBを検出することさえ扱いにくいため、これはコードを適切に書くことがプログラマーの責任である領域です。
タイプシステム - の存在とセマンティクスを含む const
キーワード - 破損するコードを書くことに対する基本的な保護を提示します。 C ++プログラマーは、このシステムを覆すことを常に認識し続ける必要があります。たとえば、ハッキングして const
ness - あなた自身の責任で行われ、一般的に悪い考えです。™
Cスタイルのキャストが生じる可能性があるという認識が不足しているケースを想像できます。
const_cast
作られていることは、気づかれることなく発生する可能性があります。
絶対。警告レベルが十分に高く設定されていると、正気なコンパイラ 五月 これについて警告することを選択しますが、それは必要ではなく、そうではないかもしれません。一般に、これはCスタイルのキャストが眉をひそめられている正当な理由ですが、Cとの後方互換性のためにまだサポートされています。
他のヒント
未定義の動作は、文字通りそれを意味します。言語標準によって定義されていない動作。通常、コードが何か間違ったことをしている状況で発生しますが、コンパイラによってエラーは検出できません。エラーをキャッチする唯一の方法は、パフォーマンスを損なうランタイムテストを導入することです。代わりに、言語仕様は、特定のことをしてはならないことを示しています。もしそうなら、何かが起こる可能性があります。
一定のオブジェクトに書き込む場合、 const_cast
コンパイル時間チェックを覆すには、3つの可能性のあるシナリオがあります。
- これは、制限されていないオブジェクトのように扱われ、それに書き込むことがそれを修正します。
- それは書き込み保護されたメモリに配置され、それに書き込むことは保護障害を引き起こします。
- コンパイルされたコードに埋め込まれた一定の値に置き換えられます(最適化中)ため、書き込み後、まだ初期値があります。
テストでは、最初のシナリオになりました。オブジェクトは(ほぼ確実に)スタックで作成されましたが、書き込み保護されていません。オブジェクトが静的である場合は2番目のシナリオ、より多くの最適化を有効にする場合は3番目のシナリオを取得することがあります。
一般に、コンパイラはこのエラーを診断することはできません - 参照またはポインターのターゲットが一定かどうかを伝える方法はありません(あなたのような非常に単純な例を除く)。あなただけを使用することを確認するのはあなた次第です const_cast
安全であることを保証できる場合 - オブジェクトが一定ではない場合、または実際にはとにかく変更しない場合。
私を困惑させているのは、これがうまくいくように見える理由です
それが未定義の動作が意味することです。
動作するように見えることを含むすべてのことができます。
最適化レベルを最上位に上げると、おそらく動作が停止します。
しかし、この動作が未定義であることを私に通知するよう警告しても私に促しません。
その時点で、オブジェクトは変更されました いいえ 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 ++キャストのポイントは次のとおりです それらを目立たせます あなたはそれらを見ることができ、一目で、危険なキャストが良性キャストの違いを見つけます。
未定義の動作 オブジェクトの生まれの方法に依存します, 、あなたは見ることができます ステファン 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
生まれた const
だから...それはそうです。
典型的な例は、保護されたデータセグメントに存在する可能性のあるconst文字列リテラルを変更しようとすることです。
コンパイラは、最適化の理由でメモリの読み取り専用部分にconstデータを配置し、このデータを変更しようとすると、UBが生成されます。
静的データとconstデータは、多くの場合、ローカル変数よりもあなたのプログラムの別の部分に保存されます。 const変数の場合、これらの領域は多くの場合、変数のconstnessを実施するために読み取り専用モードです。反応がオペレーティングシステムに依存するため、読み取り専用メモリを書き込もうとすると「未定義の動作」が生じます。 「未定義の首」とは、言語がこのケースの処理方法を指定していないことを意味します。
メモリについてより詳細な説明が必要な場合は、読むことをお勧めします これ. 。これはUNIXに基づいた説明ですが、すべてのOSで同様のメカニズムが使用されています。