Tại sao mảng C không có độ dài 0?


13

Tiêu chuẩn C11 cho biết các mảng, cả kích thước và chiều dài thay đổi "sẽ có giá trị lớn hơn 0". Biện minh cho việc không cho phép độ dài bằng 0 là gì?

Đặc biệt đối với các mảng có chiều dài thay đổi, sẽ rất hợp lý khi có kích thước bằng 0 mỗi lần. Nó cũng hữu ích cho các mảng tĩnh khi kích thước của chúng là từ một tùy chọn cấu hình macro hoặc xây dựng.

Điều thú vị là GCC (và tiếng kêu) cung cấp các tiện ích mở rộng cho phép các mảng có độ dài bằng không. Java cũng cho phép các mảng có độ dài bằng không.


7
stackoverflow.com/q/8625572 ... "Một mảng có độ dài bằng 0 sẽ khó khăn và khó hiểu để điều hòa với yêu cầu mỗi đối tượng có một địa chỉ duy nhất."
Robert Harvey

3
@RobertHarvey: Được cung cấp struct { int p[1],q[1]; } foo; int *pp = p+1;, ppsẽ là một con trỏ hợp pháp, nhưng *ppsẽ không có một địa chỉ duy nhất. Tại sao cùng một logic không thể giữ với một mảng có độ dài bằng không? Giả sử được đưa ra int q[0]; trong một cấu trúc , qsẽ đề cập đến một địa chỉ có tính hợp lệ giống như p+1ví dụ ở trên.
supercat

@DocBrown Từ tiêu chuẩn C11 6.7.6.2.5 nói về biểu thức được sử dụng để xác định kích thước của VLA "mỗi lần được đánh giá, nó sẽ có giá trị lớn hơn 0". Tôi không biết về C99 (và có vẻ kỳ lạ là họ sẽ thay đổi nó) nhưng có vẻ như bạn không thể có độ dài bằng không.
Kevin Cox

@KevinCox: có phiên bản trực tuyến miễn phí của tiêu chuẩn C11 (hoặc phần được đề cập) không?
Doc Brown

Phiên bản cuối cùng không có sẵn miễn phí (thật đáng xấu hổ) nhưng bạn có thể tải xuống bản nháp. Bản nháp cuối cùng có sẵn là open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf .
Kevin Cox

Câu trả lời:


11

Vấn đề tôi muốn đặt ra là mảng C chỉ là con trỏ đến phần đầu của một đoạn bộ nhớ được phân bổ. Có kích thước 0 có nghĩa là bạn có một con trỏ để ... không có gì? Bạn không thể có gì, vì vậy sẽ phải có một số điều tùy ý được chọn. Bạn không thể sử dụng null, bởi vì sau đó mảng 0 độ dài của bạn sẽ trông giống như con trỏ null. Và tại thời điểm đó, mọi thực hiện khác nhau sẽ chọn các hành vi độc đoán khác nhau, dẫn đến sự hỗn loạn.



7
@delnan: Chà, nếu bạn muốn mô phạm về nó, số học mảng và con trỏ được xác định sao cho một con trỏ có thể được sử dụng thuận tiện để truy cập vào một mảng hoặc để mô phỏng một mảng. Nói cách khác, đó là con trỏ số học và lập chỉ mục mảng tương đương với C. Nhưng kết quả vẫn giống nhau ... nếu độ dài của mảng bằng 0, bạn vẫn không chỉ ra gì.
Robert Harvey

3
@RobertHarvey Tất cả đều đúng, nhưng những từ kết thúc của bạn (và toàn bộ câu trả lời khi nhìn lại) có vẻ như là một cách khó hiểu và khó hiểu để giải thích rằng một mảng như vậy (tôi nghĩ đó là câu trả lời này gọi là "một đoạn bộ nhớ được phân bổ"?) sizeof0, và làm thế nào điều đó sẽ gây rắc rối. Tất cả những gì có thể được giải thích bằng cách sử dụng các khái niệm và thuật ngữ thích hợp mà không mất đi sự ngắn gọn hoặc rõ ràng. Trộn lẫn các mảng và con trỏ chỉ có nguy cơ lan truyền các mảng = quan niệm sai về con trỏ (điều này quan trọng hơn trong các bối cảnh khác) không có lợi.

2
" Bạn không thể sử dụng null, vì khi đó mảng 0 độ dài của bạn sẽ trông giống như con trỏ null " - thực sự đó chính xác là những gì Delphi làm. Các dynarrays trống và longstrings trống là con trỏ null kỹ thuật.
JensG

3
-1, tôi đầy đủ với @delnan ở đây. Điều này không giải thích được gì, đặc biệt là trong bối cảnh OP đã viết về một số trình biên dịch chính hỗ trợ khái niệm mảng có độ dài bằng không. Tôi khá chắc chắn rằng các mảng có độ dài bằng không có thể được cung cấp trong C theo cách không phụ thuộc vào việc thực hiện, không "dẫn đến hỗn loạn".
Doc Brown

6

Chúng ta hãy xem làm thế nào một mảng thường được đặt trong bộ nhớ:

         +----+
arr[0] : |    |
         +----+
arr[1] : |    |
         +----+
arr[2] : |    |
         +----+
          ...
         +----+
arr[n] : |    |
         +----+

Lưu ý rằng không có một đối tượng riêng biệt có tên arrlưu trữ địa chỉ của phần tử đầu tiên; khi một mảng xuất hiện trong một biểu thức, C sẽ tính địa chỉ của phần tử đầu tiên nếu cần.

Vì vậy, hãy nghĩ về điều này: một mảng 0 phần tử sẽ không có bộ lưu trữ dành cho nó, có nghĩa là không có gì để tính địa chỉ mảng từ đó (nói cách khác, không có ánh xạ đối tượng cho mã định danh). Nó giống như nói, "Tôi muốn tạo một intbiến không chiếm bộ nhớ." Đó là một hoạt động vô nghĩa.

Biên tập

Mảng Java là động vật hoàn toàn khác với mảng C và C ++; chúng không phải là kiểu nguyên thủy, mà là kiểu tham chiếu có nguồn gốc từ Object.

Chỉnh sửa 2

Một điểm được đưa ra trong các nhận xét bên dưới - ràng buộc "lớn hơn 0" chỉ áp dụng cho các mảng trong đó kích thước được chỉ định thông qua biểu thức không đổi ; một VLA được phép có độ dài 0 Khai báo một VLA với biểu thức không hằng có giá trị 0 không phải là một vi phạm ràng buộc, nhưng nó gọi ra hành vi không xác định.

Rõ ràng rằng VLAs là các động vật khác nhau từ các mảng thông thường và việc triển khai chúng có thể cho phép kích thước 0 . Chúng không thể được khai báo statichoặc ở phạm vi tệp, bởi vì kích thước của các đối tượng đó phải được biết trước khi chương trình bắt đầu.

Nó cũng không có giá trị gì kể từ C11, việc triển khai không bắt buộc phải hỗ trợ VLA.


3
Xin lỗi, nhưng IMHO bạn đang thiếu điểm, giống như Telastyn. Các mảng có độ dài bằng không có thể có nhiều ý nghĩa và các triển khai hiện có giống như các mảng mà OP đã nói với chúng tôi cho thấy rằng nó có thể được thực hiện.
Doc Brown

@DocBrown: Đầu tiên, tôi đã giải quyết lý do tại sao tiêu chuẩn ngôn ngữ rất có thể không cho phép họ. Thứ hai, tôi muốn một ví dụ về việc một mảng có độ dài 0 có ý nghĩa, bởi vì tôi thực sự không thể nghĩ ra một mảng. Việc thực hiện có khả năng nhất là coi T a[0]như T *a, nhưng tại sao không chỉ sử dụng T *a?
John Bode

Xin lỗi, nhưng tôi không mua "lý luận lý thuyết" cho lý do tại sao tiêu chuẩn cấm điều này. Đọc câu trả lời của tôi làm thế nào địa chỉ có thể thực sự được tính toán dễ dàng. Và tôi đề nghị bạn theo liên kết trong bình luận đầu tiên của Robert Harveys dưới câu hỏi và đọc câu trả lời thứ hai, có một ví dụ hữu ích.
Doc Brown

@DocBrown: À. Vụ structhack. Tôi chưa bao giờ sử dụng nó cá nhân; không bao giờ làm việc trên một vấn đề cần một structloại có kích thước thay đổi .
John Bode

2
Và đừng quên AFAIK kể từ C99, C cho phép các mảng có độ dài thay đổi. Và khi kích thước mảng là một tham số, việc không phải xử lý giá trị 0 như một trường hợp đặc biệt có thể làm cho rất nhiều chương trình đơn giản hơn.
Doc Brown

2

Bạn thường muốn mảng kích thước bằng không (trong thực tế biến) của bạn biết kích thước của nó trong thời gian chạy. Sau đó đóng gói nó trong một structvà sử dụng các thành viên mảng linh hoạt , như vd:

struct my_st {
   unsigned len;
   double flexarray[]; // of size len
};

Rõ ràng là thành viên mảng linh hoạt phải là người cuối cùng trong nó structvà bạn cần phải có một cái gì đó trước đó. Thường thì đó sẽ là một cái gì đó liên quan đến độ dài chiếm thời gian thực tế của thành viên mảng linh hoạt đó.

Tất nhiên bạn sẽ phân bổ:

 unsigned len = some_length_computation();
 struct my_st*p = malloc(sizeof(struct my_st)+len*sizeof(double));
 if (!p) { perror("malloc my_st"); exit(EXIT_FAILURE); };
 p->len = len;
 for (unsigned ix=0; ix<len; ix++)
    p->flexarray[ix] = log(3.0+(double)ix);

AFAIK, điều này đã có thể có trong C99, và nó rất hữu ích.

BTW, các thành viên mảng linh hoạt không tồn tại trong C ++ (vì sẽ khó xác định thời điểm và cách thức chúng nên được xây dựng & phá hủy). Tuy nhiên, hãy xem std :: dynarray trong tương lai


Bạn biết đấy, họ chỉ có thể bị hạn chế trong các loại tầm thường, và sẽ không có khó khăn gì.
Ded repeatator

2

Nếu biểu thức type name[count]được viết trong một số hàm thì bạn báo cho trình biên dịch C phân bổ trên sizeof(type)*countbyte khung ngăn xếp và tính địa chỉ của phần tử đầu tiên trong mảng.

Nếu biểu thức type name[count]được viết bên ngoài tất cả các định nghĩa hàm và cấu trúc thì bạn yêu cầu trình biên dịch C phân bổ trên các sizeof(type)*countbyte của phân đoạn dữ liệu và tính địa chỉ của phần tử đầu tiên trong mảng.

namethực sự là đối tượng hằng lưu trữ địa chỉ của phần tử đầu tiên trong mảng và mọi đối tượng lưu trữ địa chỉ của một số bộ nhớ được gọi là con trỏ, vì vậy đây là lý do bạn coi namelà con trỏ chứ không phải là mảng. Lưu ý rằng các mảng trong C chỉ có thể được truy cập thông qua các con trỏ.

Nếu countlà một biểu thức hằng ước lượng bằng 0 thì bạn yêu cầu trình biên dịch C phân bổ byte bằng 0 trên khung ngăn xếp hoặc phân đoạn dữ liệu và trả về địa chỉ của phần tử đầu tiên trong mảng, nhưng vấn đề trong việc này là phần tử đầu tiên của mảng có độ dài bằng không tồn tại và bạn không thể tính địa chỉ của thứ gì đó không tồn tại.

Đây là lý do mà yếu tố không. count+1không tồn tại trong countmảng thứ cấp, vì vậy đây là lý do mà trình biên dịch C cấm định nghĩa mảng có độ dài bằng 0 là biến trong và ngoài hàm, bởi vì nội dung của namenó là gì? Địa chỉ nào namelưu trữ chính xác?

Nếu plà một con trỏ thì biểu thức p[n]tương đương với*(p + n)

Trong đó dấu hoa thị * trong biểu thức bên phải là hoạt động bổ nhiệm của con trỏ, có nghĩa là truy cập vào bộ nhớ được trỏ bởi p + nhoặc truy cập vào bộ nhớ có địa chỉ được lưu trữ p + n, trong đó p + nlà biểu thức con trỏ, nó lấy địa chỉ pvà thêm vào địa chỉ này nnhân số kích thước của loại con trỏ p.

Có thể thêm một địa chỉ và một số?

Có, bởi vì địa chỉ là số nguyên không dấu thường được biểu diễn trong ký hiệu thập lục phân.


Nhiều trình biên dịch được sử dụng để cho phép khai báo mảng có kích thước bằng 0 trước khi Tiêu chuẩn cấm nó và nhiều trình tiếp tục cho phép các khai báo đó như một phần mở rộng. Các khai báo như vậy sẽ không gây ra vấn đề gì nếu người ta nhận ra rằng một đối tượng có kích thước NN+1các địa chỉ được liên kết, đầu tiên Nxác định các byte duy nhất và điểm cuối cùng Nmà mỗi điểm chỉ đi qua một trong các byte đó. Một định nghĩa như vậy sẽ hoạt động tốt ngay cả trong trường hợp suy biến Nlà 0.
supercat

1

Nếu bạn muốn một con trỏ đến một địa chỉ bộ nhớ, hãy khai báo một địa chỉ. Một mảng thực sự chỉ vào một đoạn bộ nhớ mà bạn đã dành riêng. Mảng phân rã thành con trỏ khi được truyền cho các hàm, nhưng nếu bộ nhớ mà chúng trỏ tới nằm trên heap, không có vấn đề gì. Không có lý do để khai báo một mảng có kích thước bằng không.


2
Nói chung, bạn sẽ không làm điều này trực tiếp nhưng là kết quả của một macro hoặc khi khai báo một mảng có chiều dài thay đổi với dữ liệu động.
Kevin Cox

Một mảng không có điểm, bao giờ. Nó có thể chứa các con trỏ và trong hầu hết các bối cảnh bạn thực sự sử dụng một con trỏ đến phần tử đầu tiên, nhưng đó là một câu chuyện khác.
Ded repeatator

1
Tên mảng IS là một con trỏ không đổi đến bộ nhớ chứa trong mảng.
ncmathsadist

1
Không, tên mảng phân rã thành một con trỏ tới phần tử đầu tiên, trong hầu hết các bối cảnh. Sự khác biệt thường rất quan trọng.
Ded repeatator

1

Từ thời của C89 ban đầu, khi Tiêu chuẩn C quy định rằng một cái gì đó có Hành vi không xác định, điều đó có nghĩa là "Làm bất cứ điều gì sẽ thực hiện trên một nền tảng đích cụ thể phù hợp nhất với mục đích của nó". Các tác giả của Tiêu chuẩn không muốn thử đoán những hành vi nào có thể phù hợp nhất cho bất kỳ mục đích cụ thể nào. Việc triển khai C89 hiện có với các tiện ích mở rộng VLA có thể có các hành vi khác nhau, nhưng hợp lý, khi được cho kích thước bằng 0 (ví dụ: một số có thể coi mảng là biểu thức địa chỉ mang lại NULL, trong khi các hành vi khác coi nó là biểu thức địa chỉ có thể bằng địa chỉ của một biến tùy ý khác, nhưng có thể không có thêm vào nó một cách an toàn mà không bị mắc bẫy). Nếu bất kỳ mã nào có thể dựa vào hành vi khác nhau như vậy, các tác giả của Tiêu chuẩn sẽ '

Thay vì cố gắng đoán những gì việc triển khai có thể làm hoặc gợi ý rằng mọi hành vi nên được coi là vượt trội so với bất kỳ hành vi nào khác, các tác giả của Tiêu chuẩn chỉ đơn giản cho phép những người thực hiện sử dụng phán quyết trong việc xử lý trường hợp đó là tốt nhất mà họ thấy phù hợp. Các triển khai sử dụng malloc () đằng sau hậu trường có thể coi địa chỉ của mảng là NULL (nếu malloc zero-zero mang lại null), những triển khai sử dụng tính toán địa chỉ ngăn xếp có thể mang lại một con trỏ khớp với một số địa chỉ của biến khác và một số triển khai khác có thể làm những thứ khác. Tôi không nghĩ rằng họ mong đợi rằng các nhà văn biên dịch sẽ cố gắng làm cho trường hợp góc không có kích thước hoạt động theo cách vô tình có chủ ý.

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.