2015-08-19 20 views
24

Mã C thích này:Tại sao mã C này có thể chạy đúng?

#include <stdio.h> 
#include <unistd.h> 
#define DIM(a) (sizeof(a)/sizeof(a[0])) 
struct obj 
{ 
    int a[1]; 
}; 
int main() 
{ 
    struct obj *p = NULL; 
    printf("%d\n",DIM(p->a)); 
    return 0; 
} 

con trỏ đối tượng này pNULL, vì vậy, tôi nghĩ rằng p->a này là bất hợp pháp. Nhưng tôi đã thử nghiệm mã này trong Ubuntu14.04, nó có thể thực thi chính xác. Vì vậy, tôi muốn biết tại sao ...


Lưu ý: mã gốc có int a[0] trên nhưng tôi đã thay đổi đó để int a[1] kể từ khi tất cả mọi người dường như được treo lên trên đó chứ không phải là thực tế câu hỏi , đó là:

Biểu thức sizeof(p->a) hợp lệ khi p bằng NULL?

+8

Không phải những gì đang xảy ra ở đây, nhưng đừng cho rằng những thứ "bất hợp pháp" (hành vi không xác định) dường như không hoạt động. Họ có thể, trên thực tế, xuất hiện để làm việc trong một thời gian rất dài trước khi một cái gì đó đơn giản như một phiên bản trình biên dịch nâng cấp phá vỡ chúng. – chris

+0

Tiêu đề cho biết C, câu đầu tiên cho biết C, các tiêu đề là C tiêu đề, bạn đã sử dụng 'printf' hơn là' cout', thậm chí không có một chút mã C++ trong câu hỏi, vì vậy tôi đang ở mất tổng cộng là tại sao nó được gắn thẻ C * và * C++ :-) Tuy nhiên, đó là một câu hỏi hay. – paxdiablo

+2

Chris Beck hoàn toàn chính xác: macro '#define DIM()' của bạn là * thời gian biên dịch *. Nó chỉ là * loại * quan trọng ở đây: không phải là giá trị thời gian chạy thực tế. – paulsm4

Trả lời

34

Bởi vì sizeof là một công trình biên dịch thời gian, nó không phụ thuộc vào việc đánh giá đầu vào. sizeof(p->a) được đánh giá dựa trên loại khai báo của thành viên p::a chỉ và trở thành một hằng số trong tệp thực thi. Vì vậy, thực tế là p điểm để null không có sự khác biệt.

Giá trị thời gian chạy là p phát hoàn toàn không có vai trò trong biểu thức sizeof(p->a).

Trong C và C++, sizeof là một nhà điều hành chứ không phải chức năng. Nó có thể được áp dụng cho một loại loại-id hoặc một biểu thức . Ngoại trừ trường hợp biểu thức và biểu thức là mảng có độ dài thay đổi (mới trong C99) (như được chỉ ra bởi paxdiablo), biểu thức là toán hạng chưa được đánh giá và kết quả giống như khi bạn đã chụp sizeof so với loại loại của biểu thức đó thay thế. (Tài liệu tham khảo Cf C11 do paxdiablo bên dưới, bản thảo làm việc C++ 14 5.3.3.1)

+2

Cũng đáng để chỉ ra 'sizeof' là toán tử chứ không phải hàm. Có thể làm giảm một số nhầm lẫn trên đường đi. – Qix

+2

Các mảng có độ dài bằng không là bất hợp pháp trong tiêu chuẩn C, nhưng được cho phép trong GNU C (được nhấn mạnh không giống như tiêu chuẩn C). Các thành viên mảng linh hoạt không được tính - thứ nguyên không có 0 nhiều như không xác định. –

+0

Đó là 'sizeof (p)' và 'sizeof ((struct obj))' không có sự khác biệt? Dù giá trị của p là bao nhiêu. –

16

Đầu tiên, nếu bạn muốn mã di động thực sự, bạn không nên cố tạo một mảng có kích thước bằng không , như bạn đã làm trong câu hỏi ban đầu của mình, hiện đã được khắc phục. Tuy nhiên, vì nó không thực sự liên quan đến câu hỏi của bạn về việc liệu sizeof(p->a) có hợp lệ khi p == NULL, chúng tôi có thể bỏ qua nó ngay bây giờ không.

Từ C11 phần 6.5.3.4 The sizeof and _Alignof operators (đậm của tôi):

2/Các nhà điều hành sizeof mang lại kích thước (tính theo byte) của toán hạng của nó, có thể là một biểu thức hoặc tên trong ngoặc của một loại. Kích thước được xác định từ loại của toán hạng. Kết quả là một số nguyên. Nếu loại toán hạng là một loại mảng có độ dài biến đổi, toán hạng được đánh giá; nếu không, toán hạng không được đánh giá và kết quả là hằng số nguyên.

Do đó không có đánh giá toán hạng nào được thực hiện trừ khi đó là mảng có độ dài thay đổi (ví dụ của bạn là không). Chỉ có loại chính nó được sử dụng để tìm ra kích thước.


Đối với các luật sư ngôn ngữ trên mạng, tiểu bang C11 trong 6.7.6.2 Array declarators (đậm của tôi):

1/Ngoài vòng loại tùy chọn và từ khóa static, các [] thể delimit một biểu thức hoặc *. Nếu họ delimit một biểu thức (trong đó xác định kích thước của một mảng), biểu thức sẽ có một loại số nguyên. Nếu biểu thức là biểu thức không đổi, thì phải có giá trị lớn hơn 0.

Tuy nhiên, vì đó là trong chế phần (nơi shallshall not không liên quan đến hành vi undefined), nó chỉ đơn giản có nghĩa là chương trình chính nó là không phù hợp chặt chẽ. Nó vẫn được bao phủ bởi chính bản thân tiêu chuẩn.

+0

Nhưng các mảng có độ dài bằng không là bất hợp pháp trong Chuẩn C, mặc dù GCC cho phép chúng như là một phần mở rộng. Các thành viên mảng linh hoạt không được tính - thứ nguyên không có 0 nhiều như không xác định. –

+0

@ Jonathan, tôi không chắc nó liên quan đến câu hỏi, vì bạn có thể sửa nó bằng cách đơn giản biến '0' thành' 1' và câu hỏi vẫn hợp lệ, vì nó có hay không 'sizeof (p-> a) 'là bất hợp pháp khi' p' là 'NULL'. Tuy nhiên, tôi sẽ đưa nó vào câu trả lời cho sự hoàn chỉnh. – paxdiablo

+0

Việc khai báo cấu trúc là bất hợp pháp - cho dù nó được sử dụng cho bất cứ điều gì hay không. Một chương trình có chứa loại cấu trúc đó không phải là tiêu chuẩn phù hợp C. –

6

Mã này chứa một vi phạm hạn chế trong ISO C vì:

mảng
struct obj 
{ 
    int a[0]; 
}; 

Zero-kích thước không được phép bất cứ nơi nào. Do đó tiêu chuẩn C không xác định hành vi của chương trình này (mặc dù there seems to be some debate về điều đó).

Mã chỉ có thể "chạy đúng" nếu trình biên dịch của bạn triển khai tiện ích mở rộng không chuẩn để cho phép mảng có kích thước bằng không.

Extensions phải được lập hồ sơ (C11 4/8), vì vậy hy vọng tài liệu hướng dẫn của trình biên dịch của bạn xác định hành vi của nó cho struct obj (một struct zero-kích thước?) Và giá trị của sizeof p->a, và có hoặc không sizeof đánh giá toán hạng của nó khi các toán hạng biểu thị một mảng có kích thước bằng không.

+3

Lưu ý rằng GCC là trình biên dịch thực hiện một phần mở rộng không chuẩn để cho phép các mảng có kích thước bằng không. –

+1

Có lẽ (sau khi đọc nhận xét cho các câu hỏi khác), bạn có thể muốn giảm âm lượng "bất hợp pháp". Nó không có nhiều bất hợp pháp vì nó không hợp lệ trong một chương trình tuân thủ nghiêm ngặt. Việc triển khai cụ thể được phép cung cấp các tiện ích mở rộng miễn là chúng không thay đổi hành vi của các chương trình tuân thủ nghiêm ngặt. Do đó chúng cũng được bao phủ bởi ISO C. Và bit giới hạn mảng có kích cỡ bằng không trong một phần ràng buộc không liên quan gì đến UB. – paxdiablo

+0

@paxdiablo đã thay đổi từ ngữ. Trong các vi phạm ràng buộc C++ rõ ràng là UB; nhưng có vẻ như các nhà văn tiêu chuẩn C đã chọn mơ hồ thay vì ... –

1

sizeof() không quan tâm đến nội dung của bất cứ điều gì, nó chỉ nhìn vào loại kết quả của biểu thức.

Kể từ C99variable length arrays, nó được tính vào thời gian chạy khi một mảng chiều dài thay đổi là một phần của biểu thức trong sizeof operand.Otherwise, các toán hạng không được đánh giá và kết quả là một integer constant

Zero-size array tờ khai trong vòng structs chưa bao giờ được cho phép bởi bất kỳ C standard, 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 incomplete array declarations with empty brackets(flexible array members).

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