2017-09-08 28 views
5

Câu hỏi này là về con trỏ bắt nguồn bằng cách sử dụng số học con trỏ với cấu trúc bù đắp.Cấu trúc bù đắp và an toàn con trỏ trong C++

Hãy xem xét các chương trình sau đây:

#include <cstddef> 
#include <iostream> 
#include <new> 

struct A { 
    float a; 
    double b; 
    int c; 
}; 

static constexpr auto off_c = offsetof(A, c); 

int main() { 
    A * a = new A{0.0f, 0.0, 5}; 
    char * a_storage = reinterpret_cast<char *>(a); 
    int * c = reinterpret_cast<int *>(a_storage + off_c)); 

    std::cout << *c << std::endl; 

    delete a; 
} 

chương trình này xuất hiện để làm việc và đưa ra kết quả mong đợi trên các trình biên dịch mà tôi thử nghiệm, sử dụng các thiết lập mặc định và C++ 11 tiêu chuẩn.

(Một chương trình có liên quan chặt chẽ, nơi mà chúng tôi sử dụng void * thay vì char *static_cast thay vì reinterpret_cast, không chấp nhận rộng rãi. gcc 5.4 vấn đề một cảnh báo về con trỏ số học với con trỏ void, và clang 6.0 nói rằng con trỏ số học với void * là một lỗi.)

Chương trình này có hành vi được xác định rõ theo tiêu chuẩn C++ không?

Câu trả lời có phụ thuộc vào việc triển khai có an toàn cho con trỏ thoải mái hay nghiêm ngặt ([basic.stc.dynamic.safety]) không?

+0

đây có phải là bài tập về nhà không? –

+2

@GarrGodfrey: Tôi sẽ ngạc nhiên ... –

+2

Tại sao không sử dụng con trỏ đến chức năng thành viên của C++. Đó là "sạch" thay thế cho 'offsetof'? –

Trả lời

8

Không có lỗi cơ bản nào trong mã của bạn.

Nếu A không phải là dữ liệu cũ, ở trên là UB (trước C++ 17) và được hỗ trợ có điều kiện (sau C++ 17).

Bạn có thể muốn thay thế char*int* bằng auto*, nhưng đó là một điều phong cách.

Lưu ý rằng các con trỏ tới các thành viên thực hiện điều tương tự chính xác này theo cách an toàn kiểu. Hầu hết các trình biên dịch thực hiện một con trỏ tới thành viên ... là phần bù của thành viên trong kiểu. Họ làm, tuy nhiên, làm việc ở khắp mọi nơi ngay cả trên cấu trúc không pod.

Bên cạnh: Tôi không thấy bảo đảm rằng offsetofconstexpr trong tiêu chuẩn. ;)

Trong mọi trường hợp, thay thế:

static constexpr auto off_c = offsetof(A, c); 

với

static constexpr auto off_c = &A::c; 

auto* a_storage = static_cast<char *>(a); 
    auto* c = reinterpret_cast<int *>(a_storage + off_c)); 

với

auto* c = &(a->*off_c); 

để thực hiện theo cách C++.

+0

Clang sẽ hét lên với bạn nếu bạn sử dụng offsetof trên một loại không POD, do đó, đó là một gợi ý tốt đẹp mà bạn đang làm những việc ít nhất một chút gần đúng: https://godbolt.org/g/6jLnM6 - bù đắp của trên loại bố trí phi tiêu chuẩn 'A' [-Winvalid-offsetof] – xaxxon

+1

Heres những gì quan tâm abt: trong 3.7.4.3 [basic.stc.dynamic.safety] nó nói con trỏ được an toàn bắt nguồn chỉ khi (điều kiện) và nếu chúng ta có chặt chẽ con trỏ an toàn sau đó một con trỏ không hợp lệ nếu nó không đến từ một nơi như vậy. Trong số học con trỏ 5.7, nó nói rằng tôi có thể làm số học thông thường trong một mảng nhưng tôi không thấy bất cứ điều gì có nói cho tôi struct số học bù đắp là ok.Im cố gắng tìm ra nếu điều này là không có liên quan theo cách tôi nghĩ là, hoặc nếu struct bù trừ số học không ok trong giả thuyết "nghiêm ngặt" impls, hoặc nếu tôi đọc 5.7 sai (n4296) –

+0

Về cơ bản tôi nghĩ rằng câu trả lời cho câu hỏi là ok của nó trong impls thoải mái và có hành vi undefined trong một impl nghiêm ngặt. Tôi nghĩ rằng khi tôi thêm một chỉ mục vào một con trỏ mà không trỏ đến một mảng, thats tự động không an toàn trong impl nghiêm ngặt, và kết quả không phải là một ptr có nguồn gốc an toàn. Và nó cũng nói dereferencing một ptr không hợp lệ là ub. Tôi nghĩ rằng điều này phải bằng cách nào đó là một lỗi soạn thảo. Nhưng tôi có thể hoàn toàn sai :) –

4

Nó an toàn trong ví dụ cụ thể của bạn, nhưng chỉ vì cấu trúc của bạn là bố cục tiêu chuẩn, bạn có thể kiểm tra kỹ bằng cách sử dụng std::is_standard_layout<>.

Đang cố gắng để áp dụng điều này để một struct như:

struct A { 
    float a; 
    double b; 
    int c; 
    std::string str; 
}; 

Sẽ là bất hợp pháp, thậm chí nếu chuỗi là quá khứ một phần của struct có liên quan.

Sửa

Heres những gì im liên quan abt: trong 3.7.4.3 [basic.stc.dynamic.safety] nó nói con trỏ có nguồn gốc một cách an toàn chỉ if (điều kiện) và nếu chúng ta có con trỏ an toàn nghiêm ngặt sau đó một con trỏ không hợp lệ nếu nó không đến từ một nơi như vậy. Trong số học con trỏ 5.7, nó nói rằng tôi có thể làm số học thông thường trong một mảng nhưng tôi không thấy bất cứ điều gì có nói cho tôi struct số học bù đắp là ok. Tôi đang cố gắng tìm hiểu xem điều này có liên quan theo cách tôi nghĩ hay không, hoặc nếu cấu trúc bù trừ số học không ok trong giả thuyết "nghiêm ngặt", hoặc nếu tôi đọc 5.7 sai (n4296)

Khi bạn đang làm con trỏ số học, bạn đang thực hiện nó trên một mảng của char, kích thước trong đó là ít nhất sizeof(A), do đó, đó là tốt.

Sau đó, khi bạn cast trở lại vào thành viên thứ hai, bạn được bảo hiểm theo (2.4):

- kết quả của một sự chuyển đổi được xác định rõ con trỏ (4.10, 5.4) của một con trỏ một cách an toàn có nguồn gốc từ giá trị;

+0

Ok, con trỏ im thêm vào có kiểu 'char *' nhưng trong 5.7.4 khối về số học trên một mảng, nó nói, "nếu con trỏ trỏ đến một phần tử của một đối tượng mảng ... "không có đối tượng mảng ở đây mặc dù theo nghĩa tiêu chuẩn. Chỉ có A. Thats cũng liên quan đến các vấn đề bí mật nghiêm ngặt: đối tượng chỉ tồn tại khi nó được tạo ra bởi ngôn ngữ, không được biến thành sự tồn tại của một diễn viên. Vì vậy, im không chắc chắn điều này được tính là thao tác con trỏ an toàn trong một mảng. Tôi nghĩ từ quan điểm tiêu chuẩn, không có mảng ở đây. Idk cho chắc chắn tho. –

1

bạn nên kiểm tra các giả định của mình.

Giả định # 1) offsetof cung cấp bù đắp chính xác theo byte. Điều này chỉ được đảm bảo nếu lớp được coi là "bố cục chuẩn", có một số hạn chế, chẳng hạn như không có phương thức ảo, tránh nhiều thừa kế, v.v. Trong trường hợp này, nó sẽ ổn, nhưng nói chung bạn không thể chắc chắn.

Giả định # 2) Một char có cùng kích thước với byte. Trong C, điều này là theo định nghĩa, vì vậy bạn được an toàn.

Giả định # 3) offsetof cung cấp bù đắp chính xác từ con trỏ đến lớp, không phải từ đầu dữ liệu. Điều này về cơ bản giống như # 1, nhưng một vtable chắc chắn có thể là một vấn đề. Một lần nữa, chỉ hoạt động với bố cục chuẩn.

+2

Giới thiệu về số 1, bạn có thể chắc chắn nếu loại có bố cục chuẩn: http://en.cppreference.com/w/cpp/types/is_standard_layout –

+0

clang cũng đưa ra cảnh báo nếu bạn gọi offsetof trên loại không phải SL – xaxxon

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