Điều gì xảy ra nếu tôi xác định một mảng có kích thước 0 trong C / C ++?


127

Chỉ tò mò, điều gì thực sự xảy ra nếu tôi xác định một mảng có độ dài bằng không int array[0];trong mã? GCC hoàn toàn không phàn nàn.

Chương trình mẫu

#include <stdio.h>

int main() {
    int arr[0];
    return 0;
}

Làm rõ

Tôi thực sự đang cố gắng tìm hiểu xem các mảng có độ dài bằng không khởi tạo theo cách này, thay vì được chỉ ra giống như độ dài thay đổi trong các nhận xét của Darhiner, có được tối ưu hóa hay không.

Điều này là do tôi phải phát hành một số mã ra ngoài tự nhiên, vì vậy tôi đang cố gắng tìm hiểu xem tôi có phải xử lý các trường hợp SIZEđược xác định là 0, xảy ra trong một số mã với định nghĩa tĩnh khôngint array[SIZE];

Tôi thực sự ngạc nhiên khi GCC không phàn nàn, dẫn đến câu hỏi của tôi. Từ những câu trả lời tôi nhận được, tôi tin rằng việc thiếu cảnh báo phần lớn là do hỗ trợ mã cũ chưa được cập nhật với cú pháp [] mới.

Bởi vì tôi chủ yếu tự hỏi về lỗi, tôi gắn thẻ câu trả lời của Lundin là chính xác (lần đầu tiên của Nawaz, nhưng nó chưa hoàn chỉnh) - những người khác đã chỉ ra cách sử dụng thực tế của nó cho các cấu trúc có đuôi, trong khi có liên quan, không phải là ' t chính xác những gì tôi đang tìm kiếm.


51
@AlexanderCorwin: Thật không may trong C ++, với hành vi không xác định, các phần mở rộng không chuẩn và các bất thường khác, việc tự mình thử một cái gì đó thường không phải là một con đường dẫn đến kiến ​​thức.
Benjamin Lindley

5
@JustinKirk Tôi cũng bị mắc kẹt bởi điều đó bằng cách thử nghiệm và thấy nó hoạt động. Và do những lời chỉ trích tôi nhận được trong bài đăng của mình, tôi đã học được rằng thử nghiệm và chứng minh nó hoạt động không có nghĩa là nó hợp lệ và hợp pháp. Vì vậy, một bài kiểm tra tự đôi khi không hợp lệ.
StormByte

2
@JustinKirk, xem câu trả lời của Matthieu để biết ví dụ về nơi bạn sẽ sử dụng nó. Nó cũng có thể có ích trong một mẫu trong đó kích thước mảng là một tham số mẫu. Ví dụ trong câu hỏi rõ ràng là ngoài ngữ cảnh.
Đánh dấu tiền chuộc

2
@JustinKirk: Mục đích của []Python hay thậm chí là ""trong C là gì? Đôi khi, bạn có một hàm hoặc một macro yêu cầu một mảng, nhưng bạn không có bất kỳ dữ liệu nào để đưa vào nó.
dan04

15
"C / C ++" là gì? Đây là hai ngôn ngữ riêng biệt
Các cuộc đua Ánh sáng trong Quỹ đạo

Câu trả lời:


86

Một mảng không thể có kích thước bằng không.

ISO 9899: 2011 6.7.6.2:

Nếu biểu thức là một biểu thức không đổi, nó sẽ có giá trị lớn hơn 0.

Các văn bản trên là đúng cho cả một mảng đơn giản (đoạn 1). Đối với VLA (mảng có độ dài thay đổi), hành vi không được xác định nếu giá trị của biểu thức nhỏ hơn hoặc bằng 0 (đoạn 5). Đây là văn bản quy phạm trong tiêu chuẩn C. Một trình biên dịch không được phép thực hiện nó khác nhau.

gcc -std=c99 -pedantic đưa ra một cảnh báo cho trường hợp không phải là VLA.


34
"Nó thực sự phải đưa ra một lỗi" - sự khác biệt giữa "cảnh báo" và "lỗi" không được nhận ra trong tiêu chuẩn (nó chỉ đề cập đến "chẩn đoán") và tình huống duy nhất mà quá trình biên dịch phải dừng lại [tức là sự khác biệt trong thế giới thực giữa cảnh báo và lỗi] là gặp phải một #errorchỉ thị.
Random832

12
FYI, theo nguyên tắc chung, các tiêu chuẩn (C hoặc C ++) chỉ nêu rõ những gì trình biên dịch phải cho phép , nhưng không phải những gì họ phải không cho phép . Trong một số trường hợp, họ sẽ nói rằng trình biên dịch sẽ đưa ra một "chẩn đoán" nhưng đó là cụ thể như họ nhận được. Phần còn lại để lại cho nhà cung cấp trình biên dịch. EDIT: Những gì Random832 nói quá.
mcmcc

8
@Lundin "Trình biên dịch không được phép xây dựng nhị phân chứa các mảng có độ dài bằng không." Các tiêu chuẩn nói hoàn toàn không có gì của loại. Nó chỉ nói rằng nó phải tạo ra ít nhất một thông báo chẩn đoán khi được cung cấp mã nguồn chứa một mảng có biểu thức hằng số có độ dài bằng không cho kích thước của nó. Tình huống duy nhất theo đó tiêu chuẩn cấm trình biên dịch xây dựng nhị phân là nếu nó gặp lệnh #errorchỉ thị tiền xử lý.
Random832

5
@Lundin Tạo nhị phân cho tất cả các trường hợp chính xác thỏa mãn # 1 và tạo hoặc không tạo một nhị phân cho các trường hợp không chính xác sẽ không ảnh hưởng đến nó. In một cảnh báo là đủ cho # 3. Hành vi này không liên quan đến # 2, vì tiêu chuẩn không xác định hành vi của mã nguồn này.
Random832

13
@Lundin: Vấn đề là tuyên bố của bạn bị nhầm lẫn; trình biên dịch tuân thủ được phép xây dựng một nhị phân có chứa một mảng có độ dài bằng không, miễn là chẩn đoán được đưa ra.
Keith Thompson

85

Theo tiêu chuẩn, nó không được phép.

Tuy nhiên, đó là cách thực hành hiện tại trong trình biên dịch C để coi các khai báo đó là khai báo thành viên mảng linh hoạt ( FAM ) :

C99 6.7.2.1, §16 : Trong trường hợp đặc biệt, phần tử cuối cùng của cấu trúc có nhiều thành viên được đặt tên có thể có kiểu mảng không hoàn chỉnh; đây được gọi là thành viên mảng linh hoạt.

Cú pháp chuẩn của FAM là:

struct Array {
  size_t size;
  int content[];
};

Ý tưởng là sau đó bạn sẽ phân bổ nó như vậy:

void foo(size_t x) {
  Array* array = malloc(sizeof(size_t) + x * sizeof(int));

  array->size = x;
  for (size_t i = 0; i != x; ++i) {
    array->content[i] = 0;
  }
}

Bạn cũng có thể sử dụng nó một cách tĩnh (phần mở rộng gcc):

Array a = { 3, { 1, 2, 3 } };

Đây còn được gọi là cấu trúc đệm đuôi (thuật ngữ này có trước khi xuất bản Tiêu chuẩn C99) hoặc cấu trúc hack (nhờ Joe Wreschnig đã chỉ ra).

Tuy nhiên, cú pháp này đã được chuẩn hóa (và các hiệu ứng được đảm bảo) chỉ gần đây trong C99. Trước khi một kích thước không đổi là cần thiết.

  • 1 là cách di động để đi, mặc dù nó khá lạ.
  • 0 đã tốt hơn trong việc chỉ ra ý định, nhưng không hợp pháp theo tiêu chuẩn liên quan và được hỗ trợ như một phần mở rộng của một số trình biên dịch (bao gồm cả gcc).

Tuy nhiên, thực tế đệm đuôi phụ thuộc vào thực tế là lưu trữ có sẵn (cẩn thận malloc) vì vậy không phù hợp với việc sử dụng ngăn xếp nói chung.


@Lundin: Tôi chưa thấy bất kỳ VLA nào ở đây, tất cả các kích thước được biết tại thời điểm biên dịch. Các mảng linh hoạt hạn xuất phát từ gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Zero-Length.html và Doe đủ điều kiện int content[];ở đây như xa như tôi hiểu. Vì tôi không quá hiểu biết về các thuật ngữ C về nghệ thuật ... bạn có thể xác nhận liệu lý luận của tôi có đúng không?
Matthieu M.

@MatthieuM.: C99 6.7.2.1, §16: Trong trường hợp đặc biệt, phần tử cuối cùng của cấu trúc có nhiều thành viên được đặt tên có thể có kiểu mảng không hoàn chỉnh; đây được gọi là thành viên mảng linh hoạt.
Christoph

Thành ngữ này còn được biết đến với cái tên "struct hack" và tôi đã gặp nhiều người quen thuộc với cái tên đó hơn là "cấu trúc đệm đuôi" (chưa bao giờ nghe thấy nó trừ khi có thể là một tham chiếu chung để đệm một cấu trúc để tương thích ABI trong tương lai ) hoặc "thành viên mảng linh hoạt" mà lần đầu tiên tôi nghe thấy trong C99.

1
Sử dụng kích thước mảng là 1 cho cấu trúc hack sẽ tránh được trình biên dịch squawk, nhưng chỉ là "di động" vì người viết trình biên dịch đủ tốt để thừa nhận việc sử dụng như một tiêu chuẩn thực tế. Nếu không cấm các mảng có kích thước bằng 0, việc sử dụng các mảng phần tử đơn lẻ của lập trình viên như là một thay thế nhàu nát và thái độ lịch sử của các nhà biên dịch rằng họ nên phục vụ nhu cầu của lập trình viên ngay cả khi Standard không yêu cầu, các nhà văn trình biên dịch có thể đã được tối ưu hóa một cách dễ dàng và hữu ích foo[x]cho foo[0]bất cứ khi nào foolà một mảng phần tử đơn.
supercat

1
@RobertSsupportsMonicaCellio: Nó được thể hiện rõ ràng trong câu trả lời, nhưng ở cuối . Tôi cũng đã tải trước lời giải thích, để làm cho nó rõ ràng hơn ngay từ đầu.
Matthieu M.

58

Trong Standard C và C ++, mảng kích thước không được phép ..

Nếu bạn đang sử dụng GCC, hãy biên dịch nó với -pedantictùy chọn. Nó sẽ đưa ra cảnh báo , nói rằng:

zero.c:3:6: warning: ISO C forbids zero-size array 'a' [-pedantic]

Trong trường hợp của C ++, nó đưa ra cảnh báo tương tự.


9
Trong Visual C ++ 2010:error C2466: cannot allocate an array of constant size 0
Đánh dấu tiền chuộc

4
-Werror chỉ đơn giản biến tất cả các cảnh báo thành lỗi, điều đó không khắc phục hành vi không chính xác của trình biên dịch GCC.
Lundin

C ++ Builder 2009 cũng đưa ra một lỗi chính xác:[BCC32 Error] test.c(3): E2021 Array must have at least one element
Lundin

1
Thay vì -pedantic -Werror, bạn cũng có thể làm-pedantic-errors
Stephan Dollberg 15/03/2016

3
Một mảng có kích thước bằng 0 không hoàn toàn giống với một mảng có kích thước bằng không std::array. (Ngoài ra: Tôi nhớ lại nhưng không thể tìm thấy nguồn mà VLAs đã được xem xét và từ chối rõ ràng khi ở trong C ++.)

27

Nó hoàn toàn bất hợp pháp và luôn luôn như vậy, nhưng rất nhiều trình biên dịch bỏ qua để báo hiệu lỗi. Tôi không chắc tại sao bạn muốn làm điều này. Cách sử dụng mà tôi biết là kích hoạt lỗi thời gian biên dịch từ boolean:

char someCondition[ condition ];

Nếu conditionlà sai, sau đó tôi nhận được một lỗi thời gian biên dịch. Vì trình biên dịch cho phép điều này, tuy nhiên, tôi đã sử dụng:

char someCondition[ 2 * condition - 1 ];

Điều này cho kích thước là 1 hoặc -1 và tôi chưa bao giờ tìm thấy trình biên dịch chấp nhận kích thước -1.


Đây là một hack thú vị để sử dụng nó cho.
Alex Koay

10
Đó là một mẹo phổ biến trong siêu lập trình, tôi nghĩ vậy. Tôi sẽ không ngạc nhiên nếu việc triển khai STATIC_ASSERTsử dụng nó.
James Kanze

Tại sao không chỉ:#if condition \n #error whatever \n #endif
Jerfov2

1
@ Jerfov2 vì điều kiện có thể không được biết tại thời điểm tiền xử lý, chỉ có thời gian biên dịch
rmeador

9

Tôi sẽ thêm rằng có cả một trang tài liệu trực tuyến của gcc về đối số này.

Một số trích dẫn:

Mảng có độ dài bằng không được phép trong GNU C.

Trong ISO C90, bạn sẽ phải cung cấp nội dung có độ dài bằng 1

Các phiên bản GCC trước 3.0 cho phép các mảng có độ dài bằng không được khởi tạo tĩnh, như thể chúng là các mảng linh hoạt. Ngoài những trường hợp hữu ích, nó cũng cho phép khởi tạo trong các tình huống sẽ làm hỏng dữ liệu sau này

vì vậy bạn có thể

int arr[0] = { 1 };

và bùm :-)


Tôi có thể làm như int a[0], sau đó a[0] = 1 a[1] = 2??
Suraj Jain

2
@SurajJain Nếu bạn muốn ghi đè lên ngăn xếp của mình :-) C không kiểm tra chỉ số so với kích thước của mảng bạn đang viết, vì vậy bạn có thể a[100000] = 5nhưng nếu may mắn, bạn sẽ đơn giản làm hỏng ứng dụng của mình, nếu bạn may mắn: -)
xanatos

Int a [0]; nghĩa là một mảng biến (mảng có kích thước bằng 0), Làm thế nào bây giờ tôi có thể gán nó
Suraj Jain

@SurajJain Phần nào của "C không kiểm tra chỉ số so với kích thước của mảng bạn đang viết" không rõ ràng? Không có kiểm tra chỉ mục trong C, bạn có thể viết sau khi kết thúc mảng và làm sập máy tính hoặc ghi đè lên các bit quý giá của bộ nhớ. Vì vậy, nếu bạn có một mảng gồm 0 phần tử, bạn có thể viết sau khi kết thúc 0 phần tử.
xanatos


9

Một cách sử dụng khác của mảng có độ dài bằng không là để tạo đối tượng có độ dài thay đổi (trước C99). Mảngđộ dài bằng không khác với mảng linh hoạt có [] không có 0.

Trích dẫn từ gcc doc :

Các mảng có độ dài bằng không được phép trong GNU C. Chúng rất hữu ích vì là phần tử cuối cùng của cấu trúc thực sự là một tiêu đề cho một đối tượng có độ dài thay đổi:

 struct line {
   int length;
   char contents[0];
 };
 
 struct line *thisline = (struct line *)
   malloc (sizeof (struct line) + this_length);
 thisline->length = this_length;

Trong ISO C99, bạn sẽ sử dụng một thành viên mảng linh hoạt, hơi khác về cú pháp và ngữ nghĩa:

  • Các thành viên mảng linh hoạt được viết dưới dạng nội dung [] không có 0.
  • Các thành viên mảng linh hoạt có kiểu không đầy đủ và do đó toán tử sizeof có thể không được áp dụng.

Một ví dụ trong thế giới thực là các mảng có độ dài bằng 0 struct kdbus_itemtrong kdbus.h (mô-đun hạt nhân Linux).


2
IMHO, không có lý do chính đáng nào để Tiêu chuẩn cấm các mảng có độ dài bằng không; nó có thể có các đối tượng có kích thước bằng 0 chỉ là thành viên của cấu trúc và coi chúng là void*mục đích của số học (vì vậy việc thêm hoặc bớt các con trỏ vào các đối tượng có kích thước bằng 0 sẽ bị cấm). Mặc dù Thành viên mảng linh hoạt hầu hết tốt hơn các mảng có kích thước bằng 0, nhưng chúng cũng có thể hoạt động như một loại "liên kết" với các bí danh mà không cần thêm một mức độ bổ sung "cú pháp" cho những gì tiếp theo (ví dụ: struct foo {unsigned char as_bytes[0]; int x,y; float z;}người ta có thể truy cập các thành viên x.. z...
supercat

... trực tiếp mà không cần phải nói myStruct.asFoo.x, v.v. Hơn nữa, IIRC, C sẽ cố gắng bao gồm một thành viên mảng linh hoạt trong một cấu trúc, do đó không thể có cấu trúc bao gồm nhiều thành viên mảng linh hoạt khác có độ dài đã biết Nội dung.
supercat

@supercat một lý do chính đáng là để duy trì tính toàn vẹn của quy tắc về việc truy cập giới hạn mảng bên ngoài. Là thành viên cuối cùng của một cấu trúc, thành viên mảng linh hoạt C99 đạt được hiệu ứng chính xác như mảng không có kích thước GCC, nhưng không cần thêm các trường hợp đặc biệt vào các quy tắc khác. IMHO đó là một cải tiến sizeof x->contentslà lỗi trong ISO C trái ngược với trả về 0 trong gcc. Mảng không có kích thước không phải là thành viên cấu trúc giới thiệu một loạt các vấn đề khác.
MM

@MM: Họ sẽ gây ra vấn đề gì nếu trừ hai con trỏ bằng nhau cho một đối tượng có kích thước bằng 0 được xác định là không có năng suất (như sẽ trừ các con trỏ bằng nhau cho bất kỳ kích thước nào của đối tượng) và trừ các con trỏ không bằng nhau vào các đối tượng có kích thước bằng 0 được xác định là năng suất Giá trị không xác định? Nếu Tiêu chuẩn đã chỉ định rằng việc triển khai có thể cho phép một cấu trúc có chứa FAM được nhúng trong một cấu trúc khác với điều kiện phần tử tiếp theo trong cấu trúc sau là một mảng có cùng kiểu phần tử với FAM hoặc một cấu trúc bắt đầu bằng một mảng như vậy và cung cấp rằng ...
supercat

... nó nhận ra FAM là bí danh của mảng (nếu quy tắc căn chỉnh sẽ khiến các mảng hạ cánh ở các độ lệch khác nhau, thì cần phải chẩn đoán), điều đó sẽ rất hữu ích. Vì vậy, không có cách nào tốt để có một phương thức chấp nhận các con trỏ tới các cấu trúc có định dạng chung struct {int n; THING dat[];}và có thể hoạt động với những thứ có thời lượng tĩnh hoặc tự động.
supercat

6

Khai báo mảng có kích thước bằng 0 trong các cấu trúc sẽ hữu ích nếu chúng được cho phép và nếu ngữ nghĩa là (1) chúng sẽ buộc căn chỉnh nhưng nếu không thì không phân bổ bất kỳ khoảng trắng nào và (2) lập chỉ mục cho mảng sẽ được coi là hành vi được xác định trong trong trường hợp con trỏ kết quả sẽ nằm trong cùng một khối bộ nhớ với cấu trúc. Hành vi như vậy không bao giờ được cho phép bởi bất kỳ tiêu chuẩn C nào, nhưng một số trình biên dịch cũ hơn đã cho phép nó trước khi nó trở thành tiêu chuẩn cho trình biên dịch để cho phép khai báo mảng không đầy đủ với dấu ngoặc rỗng.

Việc hack struct, như thường được thực hiện bằng cách sử dụng một mảng có kích thước 1, rất tinh ranh và tôi không nghĩ có bất kỳ yêu cầu nào mà trình biên dịch không thể phá vỡ nó. Ví dụ, tôi hy vọng rằng nếu một trình biên dịch nhìn thấy int a[1], nó sẽ nằm trong các quyền của nó được coi a[i]a[0]. Nếu ai đó cố gắng giải quyết các vấn đề liên kết của hack struct thông qua một cái gì đó như

typedef struct {
  kích thước uint32_t;
  dữ liệu uint8_t [4]; // Sử dụng bốn, để tránh việc đệm bị giảm kích thước của cấu trúc
}

một trình biên dịch có thể nhận được thông minh và giả sử kích thước mảng thực sự là bốn:

; Như đã viết
  foo = mySturation-> dữ liệu [i];
; Như được giải thích (giả sử phần cứng endian nhỏ)
  foo = ((* (uint32_t *) mySturation-> dữ liệu) >> (i << 3)) & 0xFF;

Tối ưu hóa như vậy có thể là hợp lý, đặc biệt là nếu myStruct->datacó thể được tải vào một thanh ghi trong cùng hoạt động như myStruct->size. Tôi không biết gì trong tiêu chuẩn sẽ cấm tối ưu hóa như vậy, mặc dù tất nhiên nó sẽ phá vỡ bất kỳ mã nào có thể mong đợi để truy cập vào công cụ ngoài yếu tố thứ tư.


1
Thành viên mảng linh hoạt đã được thêm vào C99 như một phiên bản hợp pháp của cấu trúc hack
MM

Tiêu chuẩn nói rằng việc truy cập vào các thành viên mảng khác nhau không xung đột, điều này có xu hướng làm cho việc tối ưu hóa đó là không thể.
Ben Voigt

@BenVoigt: Tiêu chuẩn ngôn ngữ C không chỉ định hiệu ứng của việc viết một byte và đọc đồng thời một từ có chứa, nhưng 99,9% bộ xử lý xác định rằng việc ghi sẽ thành công và từ đó sẽ chứa phiên bản mới hoặc cũ của byte cùng với nội dung không thay đổi của các byte khác. Nếu một trình biên dịch nhắm vào các bộ xử lý như vậy, thì xung đột sẽ là gì?
supercat

@supercat: Tiêu chuẩn ngôn ngữ C đảm bảo ghi đồng thời vào hai thành phần mảng khác nhau sẽ không xung đột. Vì vậy, đối số của bạn rằng (đọc trong khi viết) hoạt động tốt, là không đủ.
Ben Voigt

@BenVoigt: Nếu một đoạn mã là ví dụ ghi vào các phần tử mảng 0, 1 và 2 theo một chuỗi nào đó, thì sẽ không được phép đọc cả bốn phần tử thành một đoạn dài, sửa đổi ba và viết lại cả bốn, nhưng tôi nghĩ rằng nó sẽ được phép đọc cả bốn thành một đoạn dài, sửa đổi ba, viết lại 16 bit thấp hơn dưới dạng ngắn và các bit 16-23 dưới dạng một byte. Bạn có đồng ý với điều đó không? Và mã chỉ cần đọc các phần tử của mảng sẽ được phép đọc chúng thành một đoạn dài và sử dụng mã đó.
supercat
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.