2017-09-02 34 views
7

正如P0532R0解釋,在如下因素用例std::launder必須使用,以避免不確定的行爲(UB):指針算術是否傳播「洗衣」?

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,清除載體,然後推兩個新X)會發生什麼?

+2

不是'assert(p0 [1] == 43);'一個無效的表達式? ...考慮到由子表達式產生的類類型'p0 [1]'沒有重載'操作符==(X,int)' – WhiZTiM

+0

@WhiZTiM明顯的錯字顯而易見? – Barry

+0

確實是一個錯字。 – Oliv

回答

5

您的代碼調用UB,但不適用於launder的原因。這是因爲p0[1].i本身就是UB。

是的,真的([Expr.Add]/4):

當具有整體式的表達式被加到或減去一個指針,其結果具有指針操作數的類型。如果表達式P指向具有n個元素的數組對象x的元素x [i],則表達式P + J和J + P(其中J具有值j)指向(可能是假設的)元素x [i + j]如果0≤i+j≤n;否則,行爲是不確定的。同樣,如果0≤i - j≤n,則表達式P - J指向(可能是假設的)元素x [i - j]否則,行爲是不確定的。

爲此目的,不是數組元素的對象被認爲屬於單個元素數組;見8.3.1。經過n個元素的數組x中的最後一個元素的指針被認爲等同於爲此目的指向假設元素x [n]的指針;見6.9.2。

[]當應用於指針意味着做指針算術。在C++對象模型中,指針運算只能用於指向指向類型的數組中元素的指針。您始終可以將對象視爲長度爲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指向的數組和第一名稱該數組的元素,而不是第二個元素。因此,它不指向原始對象。它不是一個參考,也不是該對象的名稱。

因此,它不符合[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::launderp原始指針不會指向新構造的對象。

如果這些條件不能滿足,一個指向新的對象,可以從通過調用std​::​launder

這就是爲什麼std::launder確實what it does代表其存儲的地址的指針獲得。

[ptr.launder],恰當地名爲指針優化屏障

如果在由相同類型的現有對象佔用的存儲中創建一個新對象,一個指針到原來的對象可以被用於指新對象,除非類型包含const或引用成員;在後一種情況下,可以使用該函數來獲取指向新對象的可用指針。

這就是說原始指針不能用於引用新構造的對象,除非被清洗。

從我所看到的,它可以解釋兩種方式(可能完全錯誤)。

  • 從洗滌指針計算指針是不是原來的指針,所以它的良好的形成
  • 無處是它指出,從洗滌指針計算出的指針被清洗,所以其UB

我個人認爲第一個是真的,因爲std::launder是指針優化的障礙。