2012-10-08 25 views
15

Mỗi C ​​lập trình viên có thể xác định số phần tử trong một mảng với vĩ mô nổi tiếng này:Đáng tin cậy xác định số phần tử trong một mảng

#define NUM_ELEMS(a) (sizeof(a)/sizeof 0[a]) 

Đây là một trường hợp sử dụng điển hình:

int numbers[] = {2, 3, 5, 7, 11, 13, 17, 19}; 
printf("%lu\n", NUM_ELEMS(numbers));   // 8, as expected 

Tuy nhiên, không có gì ngăn cản các lập trình viên từ vô tình đi qua một con trỏ thay vì một mảng:

int * pointer = numbers; 
printf("%lu\n", NUM_ELEMS(pointer)); 

Trên hệ thống của tôi, điều này in 2, bởi vì rõ ràng, một con trỏ lớn gấp hai lần số nguyên. Tôi đã nghĩ về cách ngăn chặn lập trình viên truyền con trỏ do nhầm lẫn và tôi đã tìm thấy một giải pháp:

#define NUM_ELEMS(a) (assert((void*)&(a) == (void*)(a)), (sizeof(a)/sizeof 0[a])) 

Điều này hoạt động vì con trỏ tới mảng có cùng giá trị với con trỏ đến phần tử đầu tiên. Nếu bạn vượt qua một con trỏ thay vào đó, con trỏ sẽ được so sánh với một con trỏ đến chính nó, mà hầu như luôn luôn là sai. (Ngoại lệ duy nhất là một khoảng trống con trỏ đệ quy, tức là một con trỏ void trỏ đến bản thân tôi có thể sống với điều đó..)

Vô tình đi qua một con trỏ thay vì một mảng hiện nay gây ra một lỗi khi chạy:

Assertion `(void*)&(pointer) == (void*)(pointer)' failed. 

Tốt! Bây giờ tôi có một vài câu hỏi:

  1. Tôi có sử dụng assert làm toán hạng trái của biểu thức dấu phẩy hợp lệ C không? Tức là, tiêu chuẩn cho phép tôi sử dụng assert như một biểu thức? Xin lỗi nếu đây là câu hỏi ngớ ngẩn :)

  2. Kiểm tra bằng cách nào đó có thể được thực hiện tại thời gian biên dịch không?

  3. Trình biên dịch C của tôi cho rằng int b[NUM_ELEMS(a)]; là VLA. Bất kỳ cách nào để thuyết phục anh ta khác?

  4. Tôi có phải là người đầu tiên nghĩ về điều này không? Nếu vậy, có bao nhiêu trinh nữ tôi có thể mong đợi để chờ đợi tôi ở trên thiên đàng? :)

+1

Đối với một phần (4), khá chắc chắn rằng nó * không * 72. Tôi nghĩ rằng t mũ của một giá trị dành riêng cho cái gì khác ... –

+0

Bạn không có nghĩa là 'sizeof a [0]'? –

+0

Mỗi lập trình viên c đều biết rằng việc theo dõi kích thước của mảng là vấn đề của họ, không phải trình biên dịch và các thủ thuật để "tìm ra" là tiện ích giới hạn bởi vì thông tin đó chỉ được giữ lại trong phạm vi.Nếu bạn muốn trình biên dịch xử lý việc này cho bạn, hãy sử dụng một ngôn ngữ thông minh hơn. Tôi có nghĩa là, bạn chỉ cần đi đến c + + và sử dụng 'std :: vector' hoặc (với C++ 11)' std :: array', do đó, nó không phải là một thay đổi lớn. – dmckee

Trả lời

9

Is my usage of assert as the left operand of the comma expression valid standard C? That is, does the standard allow me to use assert as an expression?

Vâng, nó có giá trị như các toán hạng trái của toán tử dấu phẩy có thể là một biểu hiện của loại void.Và hàm assertvoid làm kiểu trả về.

My C compiler thinks that int b[NUM_ELEMS(a)]; is a VLA. Any way to convince him otherwise?

Nó tin như vậy vì kết quả của biểu thức dấu phẩy không bao giờ là biểu thức không đổi (e..g, 1, 2 không phải là cụm từ không đổi).

EDIT1: thêm bản cập nhật bên dưới.

Tôi có một phiên bản khác của macro của bạn mà làm việc tại thời gian biên dịch:

#define NUM_ELEMS(arr)             \ 
(sizeof (struct {int not_an_array:((void*)&(arr) == &(arr)[0]);}) * 0 \ 
    + sizeof (arr)/sizeof (*(arr))) 

và đó dường như làm việc thậm chí còn với initializer cho đối tượng với thời gian lưu trữ tĩnh. Và nó cũng làm việc một cách chính xác với ví dụ của bạn của int b[NUM_ELEMS(a)]

EDIT2:

để giải quyết @DanielFischer nhận xét. Các công trình vĩ mô trên với gccmà không-pedantic chỉ vì gcc chấp nhận:

(void *) &arr == arr 

như một biểu thức hằng số nguyên, trong khi nó coi

(void *) &ptr == ptr 

không phải là một biểu thức hằng số nguyên. Theo C, cả hai đều không phải là biểu thức hằng số nguyên và với -pedantic, gcc chính xác đưa ra chẩn đoán trong cả hai trường hợp.

Theo hiểu biết của tôi, không có cách nào 100% di động để viết macro này NUM_ELEM. C có các quy tắc linh hoạt hơn với các biểu thức hằng số khởi tạo (xem 6.6p7 trong C99) có thể được khai thác để viết macro này (ví dụ: sizeof và các ký tự hợp chất) nhưng tại phạm vi khối C không yêu cầu khởi tạo là biểu thức liên tục không thể có một macro duy nhất hoạt động trong mọi trường hợp.

EDIT3:

Tôi nghĩ rằng đó là đáng nói đến là hạt nhân Linux có một ARRAY_SIZE vĩ mô (trong include/linux/kernel.h) mà thực hiện như một tấm séc khi thưa thớt (kernel tĩnh phân tích kiểm tra) được thực thi.

giải pháp của họ là không di động và sử dụng hai phần mở rộng GNU:

  • typeof hành
  • __builtin_types_compatible_p chức năng được xây dựng trong

Về cơ bản nó trông giống như một cái gì đó như thế:

#define NUM_ELEMS(arr) \ 
(sizeof(struct {int :-!!(__builtin_types_compatible_p(typeof(arr), typeof(&(arr)[0])));}) \ 
    + sizeof (arr)/sizeof (*(arr))) 
+0

Tuyệt vời! Tôi sẽ cấp cho bạn một nửa số trinh nữ, nếu bạn đồng ý với bạn. – fredoverflow

+0

Thật không may, với '-pedantic-errors', tôi nhận được' error: bit-field 'not_an_array' width không phải là một biểu thức hằng số nguyên [-pedantic] 'từ gcc, clang đưa ra một lỗi theo mặc định: ( –

+0

@DanielFischer xem thứ hai của tôi Chỉnh sửa địa chỉ nhận xét của bạn – ouah

3
  1. Có. Biểu thức bên trái của toán tử dấu phẩy luôn được đánh giá là biểu thức void (C99 6.5.17 # 2). Vì assert() là biểu thức void, không có vấn đề gì để bắt đầu.
  2. Có thể. Mặc dù trình tiền xử lý C không biết về các loại và phôi và không thể so sánh các địa chỉ mà bạn có thể sử dụng cùng một mẹo để đánh giá sizeof() tại thời gian biên dịch, ví dụ: khai báo một mảng thứ nguyên trong đó là một biểu thức boolean. Khi 0 nó là một vi phạm ràng buộc và một chẩn đoán phải được ban hành. Tôi đã thử nó ở đây, nhưng cho đến nay vẫn chưa thành công ... có lẽ câu trả lời thực sự là "không".
  3. Không. Số khung (của các loại con trỏ) không phải là biểu thức hằng số nguyên.
  4. Có lẽ không (không có gì mới dưới mặt trời những ngày này). Một số không xác định của trinh nữ quan hệ tình dục không xác định :-)
Các vấn đề liên quan