サンプルコード:
struct S { int x; };
int func()
{
S s{2};
return (int &)s; // Equivalent to *reinterpret_cast<int *>(&s)
}
これは一般的であり、許容できると考えられています。この規格は、構造体に最初のパディングがないことを保証します。ただし、このケースは厳密なエイリアシングルール(C++ 17 [basic.lval]/11)にはリストされていません。
プログラムが次のタイプのいずれか以外のglvalueを介してオブジェクトの格納された値にアクセスしようとした場合、動作は定義されていません。
- (11.1)オブジェクトの動的タイプ、
- (11.2)オブジェクトの動的タイプのcv修飾バージョン。
- (11.3)オブジェクトの動的タイプに類似したタイプ(7.5で定義)、
- (11.4)オブジェクトの動的タイプに対応する符号付きまたは符号なしタイプであるタイプ。
- (11.5)オブジェクトの動的タイプのcv修飾バージョンに対応する符号付きまたは符号なしタイプであるタイプ。
- (11.6)要素または非静的データメンバー(再帰的に、サブアグリゲートまたは含まれるユニオンの要素または非静的データメンバーを含む)の中に前述のタイプの1つを含むアグリゲートまたはユニオンタイプ、
- (11.7)オブジェクトの動的型の(おそらくcv修飾された)基本クラス型である型、
- (11.8)char、unsigned char、またはstd :: byte型。
オブジェクトs
が保存されている値にアクセスしていることは明らかです。
箇条書きにリストされているタイプはアクセスを実行しているglvalueのタイプであり、アクセスされているオブジェクトのタイプではありません。このコードでは、glvalue型はint
であり、これは集合体または共用体型ではなく、11.6を除外します。
私の質問は次のとおりです。このコードは正しいですか。正しい場合、上記の箇条書きのどれで許可されますか?
キャストの動作は[expr.static.cast]/13になります。
タイプ「pointerto cv1
void
」のprvalueは、タイプ「pointer to cv2T
」のprvalueに変換できます。T
はオブジェクト型であり、cv2はcv1と同じか、それよりも大きいcv-qualificationです。元のポインタ値がメモリ内のバイトのアドレスA
を表し、A
がT
のアライメント要件を満たさない場合、結果のポインタ値は指定されません。 それ以外の場合、元のポインタ値がオブジェクトa
を指し、タイプb
(cv-qualificationを無視)のオブジェクトT
がある場合- pointer-interconvertiblea
を指定すると、結果はb
へのポインターになります。それ以外の場合、ポインター値は変換によって変更されません。
pointer-interconvertibleの定義は次のとおりです。
次の場合、2つのオブジェクトaとbはポインター相互変換可能です。
- それらは同じオブジェクトである、または
- 1つは共用体オブジェクトであり、もう1つはそのオブジェクトの非静的データメンバーです。
- 1つは標準レイアウトクラスオブジェクトで、もう1つはそのオブジェクトの最初の非静的データメンバーです。オブジェクトに非静的データメンバーがない場合は、そのオブジェクトの最初の基本クラスサブオブジェクトです。
- aとcがポインター相互変換可能であり、cとbがポインター相互変換可能であるようなオブジェクトcが存在します。
したがって、元のコードでは、s
とs.x
はpointer-interconvertibleであり、(int &)s
は実際にはs.x
を指定します。
したがって、厳密なエイリアシングルールでは、格納された値にアクセスするオブジェクトはs
ではなくs.x
であり、問題はありません。コードは正しいです。
expr.reinterpret.cast#11 にあると思います
オブジェクト
x
を指定するタイプT1のglvalue式は、タイプ「pointer to T1」の式の場合、タイプ「referencetoT2」にキャストできます。 reinterpret_castを使用して、タイプ「T2へのポインター」に明示的に変換できます。結果は*reinterpret_cast<T2 *>(p)
の結果です。ここで、p
はタイプ「T1へのポインター」のx
へのポインターです。一時的なものは作成されず、コピーも作成されず、コンストラクターや変換関数は呼び出されません。 [1]。
[1]結果がソースのglvalueと同じオブジェクトを参照している場合、これは型のパンニングと呼ばれることもあります。
pointer-incovertibleに関する@ M.Mの回答をサポートします:
from cppreference :
アライメント要件が満たされていると仮定すると、
reinterpret_cast
notは、を扱ういくつかの限定されたケースの外側でポインタの値を変更しますpointer-interconvertibleオブジェクト:
struct S { int a; } s;
int* p = reinterpret_cast<int*>(&s); // value of p is "pointer to s.a" because s.a
// and s are pointer-interconvertible
*p = 2; // s.a is also 2
versus
struct S { int a; };
S s{2};
int i = (int &)s; // Equivalent to *reinterpret_cast<int *>(&s)
// i doesn't change S.a;
引用された規則は、C89の同様の規則から派生したものであり、「by」という単語の意味を拡張するか、C89が作成されたときに「未定義の振る舞い」が何を意味するかを認識しない限り、記述されたとおり無意味です。 _struct S {unsigned dat[10];}s;
_のようなものを考えると、ステートメント_s.dat[1]++;
_はs
の格納された値を明確に変更しますが、その式のタイプ_struct S
_の唯一の左辺値は目的のためだけに使用されますタイプ_unsigned*
_の値を生成します。オブジェクトを変更するために使用される唯一の左辺値は、タイプint
です。
私が見ているように、この問題を解決するには2つの関連する方法があります。(1)標準の作成者が、あるタイプの左辺値が別のタイプの1つから視覚的に派生した場合を許可したいが、したくないという認識特に、コンパイラが認識しなければならないケースの範囲は、実行した最適化のスタイルと使用されていたタスクによって大幅に異なるため、どのような形式の可視派生を考慮する必要があるかについての詳細にこだわる。 (2)規格の作成者には、規格が特定の構成を有用に処理することを実際に要求しているかどうかが重要であると考える理由がないことを認識し、そうでない理由があることがすべての人に明らかであった場合。
コンパイラが次のようなものを与えたかどうかについて、委員会のメンバーの間でコンセンサスはないと思います。
_struct foo {int ct; int *dat;} it;
void test(void)
{
for (int i=0; i < it.ct; i++)
it.dat[i] = 0;
}
_
それを確実にするために要求されるべきです。 _it.ct = 1234; it.dat = &it.ct;
_の後、test();
を呼び出すと、_it.ct
_がゼロになり、他の効果はありません。理論的根拠の一部は、少なくとも一部の委員会メンバーがそう期待していることを示唆しますが、メンバータイプの任意の左辺値を使用して構造タイプのオブジェクトにアクセスできるようにするルールを省略すると、そうではないことが示唆されます。 C標準はこの問題を実際に解決したことはなく、C++標準は問題をいくらかクリーンアップしますが、実際には解決しません。