2017-09-02 7 views
7

としてはstd::launderは未定義の動作(UB)を回避するために使用されなければならないfolowingユースケースには、P0532R0で説明:"ランドリー"はポインタ算術によって伝播されていますか?

struct X{ 
    const int i; 
    x(int i):i{i}{} 
    }; 

unsigned char buff[64]; 
auto p = new(buff) X(33); 
p->~X(); 
new(buff) X(42); 
p = std::launder(p); 
assert(p->i==42); 

をしかし、これは正確である(複数のオブジェクトがバッファ上にある場合に何が起こります

unsigned char buff[64]; 
auto p0 = new(buff) X(33); 
auto p1 = new(p0+1) X(34); 
p1->~X(); 
p0->~X(); 
new(buff) X(42); 
new(p0+1) X(43); 
p0 = std::launder(p0); 
assert(p0->i==42); 
assert(p0[1].i==43);//??? 

が最後の主張は正しいですかp0[1]はまだUBを呼び出します。一つは、ベクターに2 Xをプッシュベクトルをクリアしてから)2の新しいX押すと何が起こるでしょうか?

+2

'アサート(P0 [1] == 43)ではない;'無効式? ...部分式 'p0 [1]'によって生成されるクラス型がオーバーロードされない 'operator ==(X、int)' – WhiZTiM

+0

@WhiZTiMを明らかにしているのは明らかです。 – Barry

+0

それは本当にタイプミスでした。 – Oliv

答えて

5

あなたのコードはなく、launderの理由で、UBを呼び出します。 p0[1].i自体はUBだからです。

はい、本当に([Expr.Add]/4):整数型を持つ式が追加またはポインタから減算される場合

、結果がポインタオペランドのタイプを有しています。要素に発現P点は、x [i]はn個の要素を持つ配列オブジェクトxの、式P + J及び(J値jを有する)J + Pは、(おそらくは、仮想的な)要素を指している場合、X [I + j] 0≤i + j≤nならば、それ以外の場合、動作は未定義です。同様に、式P - Jは、0≤i - j≤nの場合、(おそらく仮説的な)要素x [i - j]を指す。それ以外の場合、動作は未定義です。配列要素はない

オブジェクトは、この目的のために単一要素のアレイに属すると考えられています。 8.3.1を参照してください。 n個の要素からなる配列xの最後の要素を通るポインタは、この目的のために仮説要素x [n]へのポインタと等価であると考えられる。 6.9.2を参照してください。

[]ポインタに適用されるポインタ演算を行うことを意味します。そして、C++オブジェクトモデルでは、ポインター演算は、指されている型の配列内の要素へのポインターに対してのみ使用できます。オブジェクトは常に1の長さの配列として扱うことができるので、単一のオブジェクトの「最後の1つ」を指すポインタを得ることができます。したがって、p0 + 1が有効です。

ではないは、p0 + 1で取得したポインタを介して、そのアドレスに格納されているオブジェクトにアクセスしています。つまり、p0[1].iは未定義の動作です。これは、後に、それを洗濯だけUB 前と同じようにです。

それでは、別の可能性を見てみましょう:

X x[2]; 
x[1].~X(); //Destroy this object. 
new(x + 1) X; //Construct a new one. 

それでは、いくつかの質問をしてみましょう:

x[1] UBですか?私は言うだろう...いいえ、それはUBではありません。どうして? x[1]はないので:

元のオブジェクト、元のオブジェクトと呼ばれる参照、または元のオブジェクトの名前

x配列を指し、最初に指摘ポインタその配列の要素であり、2番目の要素ではありません。したがって、元のオブジェクトを指しません。これは参照でも、そのオブジェクトの名前でもありません。

したがって、[basic.life]/8で述べた制約の要件を満たしません。したがって、x[1]は、新しく構築されたオブジェクトを指す必要があります。

はあなたがすべてでlaunderを必要としない、ということを考えます。

あなたが合法な方法でこれをやっているのであれば、あなたはここにlaunderは必要ありません。

+0

少し待ってください。これは、 'std :: vector :: data()'によって返されたポインタのポインタ演算をUBでも行うことを意味しますか?本質的に例1のように、元々初期化されていない内部バッファで作成されたオブジェクトの集合。 –

+2

@Revolver_Ocelot正確に。 [CWG2182](https://wg21.link/CWG2182)を参照してください。 – Barry

+1

@Revolver_Ocelot:いいえ、 'vector'が返すものに対してポインタ演算を行っても問題ありません。しかし、それは、標準*が明示的に*それが上手であることを明示しているからです。 'vector'は標準ライブラリの一部であるため、ユーザのためにUBとなる実装定義のものを行うことができます。要するに、この規則は、あなた自身が合法的に 'vector'を実装できないことを意味します。 –

2

オブジェクトの寿命が終了した後に、場合std::launderが最初の場所で必要とされる理由は[basic.life]

からこの違反によるもので占められたオブジェクトを再利用または解放された記憶の前に、新しいオブジェクトは、元のオブジェクトが占有していた記憶場所、元のオブジェクトを指し示すポインタ、元のオブジェクトを参照する参照、または元のオブジェクトの名前で作成され、自動的に新しいオブジェクトを参照し、新しいオブジェクトのライフタイムが開始され、新しいオブジェクトを操作するために使用することができます:[...]

  • 元のオブジェクトの型はconst修飾子ではなく、クラス型の場合は型がconst修飾型または参照型でない非静的データメンバーを含まず、[...]
  • std::launder無ししたがって

p元のポインタが新たに構築されたオブジェクトを指していないであろう。これらの条件が満たされない場合

は、新しいオブジェクトへのポインタは、std::launderwhat it doesを行う理由ですstd​::​launder

を呼び出すことによって、そのストレージのアドレスを表すポインタから取得することができます。 [ptr.launder]から

、適切標題ポインタ最適化バリア

新しいオブジェクトが同じタイプの既存のオブジェクトによって占有ストレージに作成されている場合、元のオブジェクトへのポインタを参照するために使用することができます型にconstまたは参照メンバが含まれていない場合は新しいオブジェクト。後者の場合、この関数を使用して新しいオブジェクトへの使用可能なポインタを取得できます。

つまり、元のポインタは、洗濯されない限り、新しく構築されたオブジェクトを参照するために使用することはできません。

私が見ることから、両方の方法で解釈できます(完全に間違っている可能性があります)。

  • それがどこ
  • 整形よう洗濯ポインタから計算ポインタは、元のポインタでないことは、そのUB
ので、洗濯ポインタから算出ポインタが洗濯されることを述べています

は、私は個人的にポインタの最適化の障壁であることに起因std::launderに、最初は真実であると信じています。

関連する問題