Làm thế nào cấu trúc này có thể có sizeof == 0?


80

Có một bài viết cũ yêu cầu một cấu trúc sizeofsẽ trở lại 0. Có một số câu trả lời điểm cao từ những người dùng có uy tín cao nói rằng theo tiêu chuẩn không có loại hoặc biến nào có thể có kích thước bằng 0. Và tôi đồng ý 100% với điều đó.

Tuy nhiên, có câu trả lời mới này trình bày giải pháp này:

struct ZeroMemory {
    int *a[0];
};

Tôi chỉ định bỏ phiếu và bình luận về nó, nhưng thời gian ở đây đã dạy tôi kiểm tra ngay cả những thứ mà tôi chắc chắn 100%. Vì vậy ... trước sự ngạc nhiên của tôi cả gccclanghiển thị kết quả tương tự: sizeof(ZeroMemory) == 0. Hơn nữa, kích thước của một biến là 0:

ZeroMemory z{};
static_assert(sizeof(z) == 0); // Awkward...

Cái gì ...?

Liên kết chốt chặn

Sao có thể như thế được?


37
Mảng kích thước 0 không phải là tiêu chuẩn C ++. nhưng phần mở rộng.
Jarod42

@ Jarod42 giờ thì rõ ràng rồi.
bolov

1
Ngoài ra nó không biên dịch theo MSVC
UnholySheep

7
Bây giờ hãy đặt chúng trong một mảng và thực hiện số học con trỏ trên chúng.
CodesInChaos

Hmmm ... Không nên lấy bao nhiêu byte nếu không phải là 0?
Gerhardh

Câu trả lời:


52

Trước khi C được chuẩn hóa, nhiều trình biên dịch sẽ không gặp khó khăn gì khi xử lý các loại kích thước bằng không miễn là mã không bao giờ cố gắng trừ một con trỏ đến một loại kích thước bằng không từ một con trỏ khác. Những loại như vậy rất hữu ích, và hỗ trợ chúng dễ dàng hơn và rẻ hơn so với việc cấm chúng. Tuy nhiên, các trình biên dịch khác đã quyết định cấm các kiểu như vậy và một số mã xác nhận tĩnh có thể đã dựa trên thực tế là chúng sẽ gây nhiễu nếu mã cố gắng tạo một mảng có kích thước bằng không. Các tác giả của Tiêu chuẩn đã phải đối mặt với một sự lựa chọn:

  1. Cho phép trình biên dịch chấp nhận âm thầm khai báo mảng có kích thước bằng không, ngay cả trong trường hợp mục đích của khai báo đó là kích hoạt quá trình biên dịch chẩn đoán và hủy bỏ, đồng thời yêu cầu tất cả trình biên dịch chấp nhận các khai báo đó (mặc dù không nhất thiết phải âm thầm) như tạo ra các đối tượng có kích thước bằng không .

  2. Cho phép trình biên dịch chấp nhận một cách âm thầm các khai báo mảng có kích thước bằng 0, ngay cả trong trường hợp mục đích của các khai báo đó là kích hoạt quá trình biên dịch chẩn đoán và hủy bỏ, đồng thời cho phép các trình biên dịch gặp phải các khai báo như vậy để hủy biên dịch hoặc tiếp tục nó khi rảnh rỗi.

  3. Yêu cầu các triển khai đưa ra một chẩn đoán nếu mã khai báo một mảng có kích thước bằng 0, nhưng sau đó cho phép các triển khai hủy bỏ quá trình biên dịch hoặc tiếp tục nó (với bất kỳ ngữ nghĩa nào họ thấy phù hợp) khi họ rảnh rỗi.

Các tác giả của Tiêu chuẩn đã chọn # 3. Do đó, khai báo mảng có kích thước bằng không được coi là "phần mở rộng" của Tiêu chuẩn, mặc dù các cấu trúc như vậy đã được hỗ trợ rộng rãi trước khi Tiêu chuẩn cấm chúng.

Tiêu chuẩn C ++ cho phép tồn tại các đối tượng rỗng, nhưng với nỗ lực cho phép địa chỉ của các đối tượng trống có thể sử dụng được dưới dạng mã thông báo, nó yêu cầu chúng có kích thước tối thiểu là 1. Đối với một đối tượng không có thành viên nào phải có kích thước 0 do đó sẽ vi phạm Tiêu chuẩn. Tuy nhiên, nếu một đối tượng chứa các thành viên có kích thước bằng không, thì Tiêu chuẩn C ++ không áp đặt yêu cầu nào về cách nó được xử lý ngoài việc chương trình có chứa khai báo như vậy phải kích hoạt chẩn đoán. Vì hầu hết các mã sử dụng các khai báo như vậy mong đợi các đối tượng kết quả có kích thước bằng 0, hành vi hữu ích nhất cho các trình biên dịch nhận mã đó là xử lý chúng theo cách đó.


"Trước khi C được chuẩn hóa", ý bạn là trước khi C99 tồn tại?
Stargateur

1
@Stargateur: Ý tôi là khoảng 15 năm từ khi C được phát minh đến khi tiêu chuẩn C89 được viết ra. C99 đã thêm lại một dạng giới hạn của đối tượng kích thước bằng không để tránh một số kludges cần thiết để giải quyết vấn đề cấm các mảng có kích thước bằng không, nhưng các kludges như vậy sẽ không cần thiết nếu C89 không cấm các mảng có kích thước 0 một cách khó chịu trong nơi đầu tiên.
supercat

"Loại như vậy hữu ích" Chúng hữu ích như thế nào?
dùng109923

1
@ user109923: Như một ví dụ: struct polygon { int count; POINT sides[0];}; struct { struct polygon poly; POINT pts[3]; } myTriangle = { {3}, {{1,1},{2,2},{2,1} };. Người ta sẽ cần phải thận trọng để đảm bảo việc căn chỉnh không gây ra vấn đề, nhưng người ta có thể có các đối tượng có thời lượng tĩnh ở các kích thước khác nhau có thể được xử lý thay thế cho nhau.
supercat

42

Như đã chỉ ra bởi Jarod42, mảng kích thước 0 không phải là C ++ tiêu chuẩn, mà là phần mở rộng GCC và Clang.

Việc thêm sẽ -pedantictạo ra cảnh báo này:

5 : <source>:5:12: warning: zero size arrays are an extension [-Wzero-length-array]
    int *a[0];
           ^

Tôi luôn quên rằng std=c++XX(thay vì std=gnu++XX) không tắt tất cả các tiện ích mở rộng.

Điều này vẫn không giải thích sizeofhành vi. Nhưng ít nhất chúng ta biết nó không chuẩn ...


1
Dù bằng cách nào nó có gì ngạc nhiên khi ngay cả một struct có sản phẩm nào nên sản xuất khác không giá trị sizeof ...
WF

2
Điều thú vị là nếu bạn tạo ra hai trường hợp tự động, chúng được lưu trữ sizeof(int*)ngoài (tôi giả sử): coliru.stacked-crooked.com/a/e9a3038bf587b65c
eerorika

9
Điều này chỉ trả lời rằng mảng kích thước 0 là không chuẩn nhưng nó không giải thích câu hỏi bạn đã hỏi.
haccks

1
Hành vi không nhất thiết phải có ý nghĩa theo bất kỳ cách nào theo tiêu chuẩn - nó chỉ cần có ý nghĩa đối với những người triển khai trình biên dịch. Trình biên dịch đã cho phép một kiểu được định nghĩa theo cách bị tiêu chuẩn cấm. Do đó, các yêu cầu từ tiêu chuẩn về kết quả sizeofcung cấp cho loại đó cũng không được áp dụng. Do đó, hành vi là quyết định của các nhà phát triển trình biên dịch.
Peter

1
Ý tưởng là ZeroMemory* z = (ZeroMemory*)malloc(sizeof(ZeroMemory) + 2 * sizeof(int*)); z.a[1] = new int{1}; /* or whatever, just use indices into a to access dynamic memory after the previous members (in this case: none) */Tất nhiên, điều này không tương thích tiêu chuẩn! (Điều này có thể được sử dụng để dễ dàng phân tích cú pháp các tin nhắn mạng có độ dài động bằng cách truyền đến một cấu trúc chẳng hạn).
hoffmale

19

Trong C ++, mảng có kích thước bằng 0 là không hợp lệ.

ISO / IEC 14882: 2003 8.3.4 / 1:

[..] Nếu biểu thức hằng số (5.19) có mặt, nó sẽ là một biểu thức hằng số tích phân và giá trị của nó phải lớn hơn 0 . Biểu thức hằng xác định giới hạn của (số phần tử trong) mảng. Nếu giá trị của biểu thức hằng là N, mảng có Ncác phần tử được đánh số 0đến N-1và kiểu của số nhận dạng Dlà “ mảng danh sách kiểu khai báo có nguồn gốc của NT”. [..]

g ++ yêu cầu -pedanticcờ đưa ra cảnh báo trên một mảng có kích thước bằng không.


5

Mảng có độ dài bằng không là phần mở rộng của GCC và Clang. Áp dụng sizeofcho các mảng có độ dài bằng 0 sẽ cho kết quả là 0 .

Một lớp C ++ (trống) không thể có kích thước 0, nhưng lưu ý rằng lớp đó ZeroMemorykhông trống. Nó có một thành viên được đặt tên với kích thước 0và việc áp dụng sizeofsẽ trả về số không.


5
vì vậy ... một lớp trống không thể có kích thước 0, nhưng một lớp không trống có thể có kích thước 0... điều này không có ý nghĩa nhiều. Nhưng tôi đoán đây là loại trường hợp xung đột mà bạn gặp phải với các phần mở rộng không chuẩn.
bolov

@bolov; Một lớp c ++ (trống) không thể có kích thước 0 theo tiêu chuẩn cho lớp trống. Tiêu chuẩn C ++ nói rằng vì không có cách nào khác để tạo cấu trúc có kích thước 0. Trong trường hợp cụ thể này, thành viên của struct tự nó có kích thước 0 và điều này làm cho kích thước của struct 0. Tôi biết nó khó hiểu nhưng tôi đã cố gắng hết sức để giải thích.
haccks

3
@bodov: Nhưng đây không phải là lớp C ++, mà là lớp G ++, vì vậy các quy tắc C ++ không cần phải áp dụng cho nó. Đặc biệt lưu ý rằng bạn không thể có một mảng kiểu này hoặc làm toán với các con trỏ của nó, do đó, lý do thông thường rằng kích thước không thể bằng 0 cũng không phù hợp.
Ben Voigt
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.