2017-09-02 14 views
7

Như đã giải thích trong P0532R0, trong trường hợp sử dụng folowing std::launder phải được sử dụng để tránh những hành vi không xác định (UB):Có phải "đồ giặt" được nhân giống bằng số học con trỏ không?

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); 

Nhưng những gì xảy ra trong trường hợp có nhiều hơn một đối tượng là trên đệm (điều này là chính xác điều gì sẽ xảy ra nếu một đẩy 2 X trong một vector, xóa vector và sau đó đẩy hai mới X):

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);//??? 

là sự khẳng định cuối cùng chính xác, hoặc p0[1] vẫn gọi UB?

+2

Không phải là 'khẳng định (p0 [1] == 43);' một biểu thức không hợp lệ? ... Xem xét loại lớp được tạo ra bởi biểu thức con 'p0 [1]' không có toán tử 'quá tải == (X, int)' – WhiZTiM

+0

@WhiZTiM Lỗi đánh máy rõ ràng là hiển nhiên? – Barry

+0

Đó là một lỗi đánh máy. – Oliv

Trả lời

5

Mã của bạn gọi UB, nhưng không phải vì lý do launder. Đó là bởi vì p0[1].i chính là UB.

Vâng, thực sự ([Expr.Add]/4):

Khi một biểu thức có kiểu không thể thiếu được thêm vào hoặc trừ từ một con trỏ, kết quả có kiểu của toán hạng trỏ. Nếu biểu thức P trỏ đến phần tử x [i] của một đối tượng mảng x với các phần tử n, các biểu thức P + J và J + P (trong đó J có giá trị j) trỏ đến phần tử (có thể giả định) x [i + j] nếu 0 ≤ i + j ≤ n; nếu không, hành vi là không xác định. Tương tự như vậy, biểu thức P - J trỏ tới phần tử (có thể giả thiết) x [i - j] nếu 0 ≤ i - j ≤ n; nếu không, hành vi là không xác định.

Một đối tượng không phải là phần tử mảng được coi là thuộc về mảng đơn nguyên tố cho mục đích này; xem 8.3.1. Một con trỏ qua phần tử cuối cùng của một mảng x của n phần tử được coi là tương đương với một con trỏ tới một phần tử giả thuyết x [n] cho mục đích này; xem 6.9.2.

[] khi áp dụng cho con trỏ có nghĩa là làm số học con trỏ. Và trong mô hình đối tượng C++, số học con trỏ chỉ có thể được sử dụng trên con trỏ tới các phần tử trong một mảng của kiểu được trỏ tới. Bạn luôn có thể coi một đối tượng là một mảng có độ dài 1, vì vậy bạn có thể nhận được một con trỏ tới "một trong quá khứ kết thúc" của đối tượng đơn lẻ. Do đó, p0 + 1 hợp lệ.

không hợp lệ là truy cập đối tượng được lưu trữ tại địa chỉ đó mặc dù con trỏ thu được qua p0 + 1. Tức là, p0[1].i là hành vi không xác định. Đây chỉ là UB trước khi rửa nó như sau.

Bây giờ, chúng ta hãy nhìn vào một khả năng khác nhau:

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

Vì vậy, hãy hỏi một số câu hỏi:

x[1] UB? Tôi sẽ nói ... không, nó không phải UB. Tại sao? Bởi vì x[1] không phải là:

một con trỏ chỉ ra rằng những đối tượng ban đầu, một tài liệu tham khảo mà gọi các đối tượng ban đầu, hoặc tên của đối tượng gốc

x điểm đến mảng và là người đầu tiên phần tử của mảng đó, không phải phần tử thứ hai. Do đó, nó không trỏ đến đối tượng gốc. Nó không phải là một tham chiếu, cũng không phải là tên của đối tượng đó.

Do đó, nó không đủ điều kiện cho các hạn chế được nêu bởi [basic.life]/8. Vì vậy, x[1] nên trỏ đến đối tượng mới được xây dựng.

Cho rằng, bạn không cần launder chút nào.

Vì vậy, nếu bạn làm điều này theo cách hợp pháp, thì bạn không cần launder tại đây.

+0

Đợi một chút. Điều đó có nghĩa là làm số học con trỏ trên con trỏ được trả về bởi 'std :: vector :: data()' là UB? Vì nó tinh túy một ví dụ 1: một tập hợp các đối tượng được tạo ra trong một số bộ đệm nội bộ ban đầu chưa được khởi tạo. –

+2

@Revolver_Ocelot Chính xác. Xem [CWG2182] (https://wg21.link/CWG2182). – Barry

+1

@Revolver_Ocelot: Không, làm số học con trỏ trên những gì 'vector' trả về là tốt. Nhưng đó là vì tiêu chuẩn * chỉ rõ * rằng nó là tốt. 'vectơ' là một phần của thư viện chuẩn và do đó được phép thực hiện những điều được định nghĩa thực hiện, đó là UB cho người dùng. Tóm lại, quy tắc này có nghĩa là bạn không thể tự mình thực thi 'vectơ' một cách hợp pháp. –

2

Lý do std::launder là cần thiết ở nơi đầu tiên là do vi phạm này từ [basic.life]

Nếu sau thời gian tồn tại của một đối tượng đã kết thúc và trước khi lưu trữ mà các đối tượng chiếm được tái sử dụng hoặc phát hành, một đối tượng mới được tạo tại vị trí lưu trữ mà đối tượng gốc chiếm giữ, một con trỏ trỏ đến đối tượng ban đầu, tham chiếu được gọi đến đối tượng ban đầu hoặc tên của đối tượng gốc sẽ tự động tham chiếu đến đối tượng mới và một khi tuổi thọ của đối tượng mới đã bắt đầu, có thể được sử dụng để thao tác đối tượng mới, nếu: [...]

  • loại đối tượng gốc không đủ điều kiện, và, nếu loại lớp, không chứa bất kỳ thành viên dữ liệu không tĩnh nào có loại đủ điều kiện hoặc loại tham chiếu và [...]

Do đó không có std::launder, p con trỏ ban đầu sẽ không trỏ đến đối tượng mới được xây dựng.

Nếu những điều kiện không được đáp ứng, một con trỏ đến đối tượng mới có thể thu được từ một con trỏ đại diện cho địa chỉ dung lượng lưu trữ của mình bằng cách gọi std​::​launder

Đó là lý do tại sao std::launder không what it does.

Từ [ptr.launder] với tên gọi rào cản tối ưu hóa Pointer

Nếu một đối tượng mới được tạo ra trong lưu trữ bị chiếm đóng bởi một đối tượng hiện cùng loại, một con trỏ đến đối tượng ban đầu có thể được sử dụng để tham khảo đối tượng mới trừ khi loại chứa const hoặc các thành viên tham chiếu; trong các trường hợp sau, hàm này có thể được sử dụng để có được một con trỏ có thể sử dụng được đối tượng mới.

Điều này nói rằng con trỏ ban đầu không thể được sử dụng để chỉ đối tượng mới được xây dựng trừ khi được giặt.

Từ những gì tôi có thể thấy, nó có thể được diễn giải theo cả hai cách (có thể hoàn toàn bị nhầm lẫn).

  • Một con trỏ tính toán từ một con trỏ giặt không phải là con trỏ gốc, do đó nó cũng như hình thành
  • Nowhere là nó nói rằng một con trỏ tính toán từ một con trỏ giặt là giặt, vì vậy nó UB

Cá nhân tôi tin rằng điều đầu tiên là đúng, do std::launder là rào cản tối ưu hóa con trỏ.

Các vấn đề liên quan