Làm cách nào để sử dụng “sizeof” trong macro bộ xử lý?


95

Có cách nào để sử dụng sizeofmacro trong bộ xử lý trước không?

Ví dụ, đã có rất nhiều tình huống trong nhiều năm mà tôi muốn làm điều gì đó như:

#if sizeof(someThing) != PAGE_SIZE
#error Data structure doesn't match page size
#endif

Điều chính xác mà tôi đang kiểm tra ở đây hoàn toàn được tạo ra - vấn đề là, tôi thường thích đưa vào các loại kiểm tra thời gian biên dịch (kích thước hoặc căn chỉnh) này để đề phòng ai đó sửa đổi cấu trúc dữ liệu có thể điều chỉnh sai hoặc lệch kích thước những thứ có thể phá vỡ chúng.

Không cần phải nói - Tôi dường như không thể sử dụng một sizeoftheo cách được mô tả ở trên.


Đây là lý do chính xác tại sao các hệ thống xây dựng tồn tại.
Šimon Tóth

3
Đây là lý do chính xác tại sao các chỉ thị #error luôn phải ở trong dấu ngoặc kép (hằng số ký tự không kết thúc do "not").
Jens,

1
Xin chào @Brad. Vui lòng xem xét việc thay đổi câu trả lời được chấp nhận của bạn thành câu trả lời là nevermind, bởi vì trong thời gian này, câu trả lời được chấp nhận hiện tại hơi lỗi thời.
Bodo Thiesen

@BodoThiesen Đã xong.
Brad

Câu trả lời:


69

Có một số cách để làm điều này. Các đoạn mã sau sẽ không tạo ra mã nào nếu sizeof(someThing)bằng PAGE_SIZE; nếu không, chúng sẽ tạo ra lỗi thời gian biên dịch.

1. C11 cách

Bắt đầu với C11, bạn có thể sử dụng static_assert(yêu cầu #include <assert.h>).

Sử dụng:

static_assert(sizeof(someThing) == PAGE_SIZE, "Data structure doesn't match page size");

2. Macro tùy chỉnh

Nếu bạn chỉ muốn gặp lỗi thời gian biên dịch khi sizeof(something)không như bạn mong đợi, bạn có thể sử dụng macro sau:

#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))

Sử dụng:

BUILD_BUG_ON( sizeof(someThing) != PAGE_SIZE );

Bài viết này giải thích chi tiết tại sao nó hoạt động.

3. MS cụ thể

Trên trình biên dịch C ++ của Microsoft, bạn có thể sử dụng macro C_ASSERT (yêu cầu #include <windows.h>), sử dụng một thủ thuật tương tự như một thủ thuật được mô tả trong phần 2.

Sử dụng:

C_ASSERT(sizeof(someThing) == PAGE_SIZE);

4
... thật điên rồ. Tại sao đây không phải là câu trả lời được chấp nhận, @Brad (OP)?
Kỹ sư

Tham khảo tốt cho BUILD_BUG_ON.
Petr Vepřek

2
Macro không hoạt động trong GNU gcc(được thử nghiệm tại phiên bản 4.8.4) (Linux). Tại ((void)sizeof(...lỗi với expected identifier or '(' before 'void'expected ')' before 'sizeof'. Nhưng về nguyên tắc size_t x = (sizeof(...thay vào đó nó hoạt động như dự định. Bạn phải "sử dụng" kết quả, bằng cách nào đó. Để cho phép điều này được gọi nhiều lần bên trong một hàm hoặc ở phạm vi toàn cục, một cái gì đó giống như extern char _BUILD_BUG_ON_ [ (sizeof(...) ];có thể được sử dụng nhiều lần (không có tác dụng phụ, không thực sự tham chiếu ở _BUILD_BUG_ON_bất kỳ đâu).
JonBrave

Đã sử dụng xác nhận tĩnh lâu hơn rất nhiều so với năm 2011 đã được một năm.
Dan

1
@Engineer nhìn đi, sự điên rồ đã dừng lại;)
Bodo Thiesen

70

Có cách nào để sử dụng " sizeof" trong macro bộ xử lý trước không?

Không. Các lệnh điều kiện có một tập hợp các biểu thức điều kiện bị hạn chế; sizeoflà một trong những điều không được phép.

Các chỉ thị tiền xử lý được đánh giá trước khi nguồn được phân tích cú pháp (ít nhất là về mặt khái niệm), do đó, chưa có bất kỳ loại hoặc biến nào có được kích thước của chúng.

Tuy nhiên, có các kỹ thuật để nhận các xác nhận thời gian biên dịch trong C (ví dụ: xem trang này ).


Bài báo tuyệt vời - giải pháp thông minh! Mặc dù bạn phải quản trị viên - họ thực sự đã đẩy cú pháp C đến giới hạn để làm cho cú pháp này hoạt động! : -O
Brad

1
Hóa ra - như bài báo thậm chí còn nói - tôi đang xây dựng mã nhân Linux ngay bây giờ - và có một định nghĩa đã có trong hạt nhân - BUILD_BUG_ON - nơi hạt nhân sử dụng nó cho những thứ như: BUILD_BUG_ON (sizeof (char)! = 8)
Brad

2
@Brad BUILD_BUG_ON và những người khác tạo mã chắc chắn không chính xác sẽ không biên dịch được (và đưa ra một số thông báo lỗi không rõ ràng trong quá trình xử lý). Không thực sự là câu lệnh #if, vì vậy, bạn không thể loại trừ khối mã dựa trên điều này.
keltar

10

Tôi biết đó là một câu trả lời muộn, nhưng để thêm vào phiên bản của Mike, đây là phiên bản chúng tôi sử dụng không phân bổ bất kỳ bộ nhớ nào. Tôi đã không đưa ra kiểm tra kích thước ban đầu, tôi đã tìm thấy nó trên internet nhiều năm trước và rất tiếc không thể tham khảo tác giả. Hai phần còn lại chỉ là phần mở rộng của cùng một ý tưởng.

Bởi vì chúng là của typedef, không có gì được cấp phát. Với __LINE__ trong tên, nó luôn là một tên khác để có thể sao chép và dán khi cần thiết. Điều này hoạt động trong trình biên dịch MS Visual Studio C và trình biên dịch GCC Arm. Nó không hoạt động trong CodeWarrior, CW phàn nàn về việc xác định lại, không sử dụng cấu trúc tiền xử lý __LINE__.

//Check overall structure size
typedef char p__LINE__[ (sizeof(PARS) == 4184) ? 1 : -1];

//check 8 byte alignment for flash write or similar
typedef char p__LINE__[ ((sizeof(PARS) % 8) == 0) ? 1 : 1];

//check offset in structure to ensure a piece didn't move
typedef char p__LINE__[ (offsetof(PARS, SUB_PARS) == 912) ? 1 : -1];

Điều này thực sự hoạt động thực sự tốt cho một dự án C tiêu chuẩn ... Tôi thích nó!
Ashley Duncan

1
Câu này phải là câu trả lời chính xác vì phân bổ bằng không. Thậm chí tốt hơn vào một định nghĩa:#define STATIC_ASSERT(condition) typedef char p__LINE__[ (condition) ? 1 : -1];
Renaud Cerrato

p__LINE__ không tạo ra một tên duy nhất. Nó tạo ra p__LINE__ dưới dạng một biến. Bạn sẽ cần một macro preroc và sử dụng __CONCAT từ sys / cdefs.h.
Coroos

9

Tôi biết chủ đề này thực sự cũ nhưng ...

Giải pháp của tôi:

extern char __CHECK__[1/!(<<EXPRESSION THAT SHOULD COME TO ZERO>>)];

Miễn là biểu thức đó bằng 0, nó sẽ biên dịch tốt. Bất cứ điều gì khác và nó sẽ nổ tung ngay tại đó. Bởi vì biến là extern nên nó sẽ không chiếm dung lượng và miễn là không ai tham chiếu đến nó (mà họ sẽ không tham khảo) thì nó sẽ không gây ra lỗi liên kết.

Không linh hoạt như macro khẳng định, nhưng tôi không thể lấy nó để biên dịch trong phiên bản GCC của mình và điều này sẽ ... và tôi nghĩ nó sẽ biên dịch ở bất cứ đâu.


6
Đừng bao giờ phát minh ra các macro của riêng bạn bắt đầu bằng hai dấu gạch dưới. Con đường này nằm ở sự điên rồ (hay còn gọi là hành vi không xác định ).
Jens

Có một loạt các ví dụ được liệt kê trên trang này pixelbeat.org/programming/gcc/static_assert.html
portforwardpodcast

không hoạt động khi được biên dịch bằng trình biên dịch arm gcc. đưa ra lỗi dự kiến ​​"error: ' CHECK ' đã được sửa đổi có thể thay đổi ở phạm vi tệp"
thunderbird

@Jens Bạn nói đúng nhưng đây thực sự không phải là macro, nó là một khai báo biến. Tất nhiên, nó có thể can thiệp vào macro.
Melebius

4

Các câu trả lời hiện có chỉ cho thấy cách đạt được hiệu quả của "xác nhận thời gian biên dịch" dựa trên kích thước của một kiểu. Điều đó có thể đáp ứng nhu cầu của OP trong trường hợp cụ thể này, nhưng có những trường hợp khác mà bạn thực sự cần một bộ tiền xử lý có điều kiện dựa trên kích thước của một loại. Đây là cách thực hiện:

Viết cho mình một chương trình C nhỏ như:

/* you could call this sizeof_int.c if you like... */
#include <stdio.h>
/* 'int' is just an example, it could be any other type */
int main(void) { printf("%zd", sizeof(int); }

Biên dịch đó. Viết một tập lệnh bằng ngôn ngữ kịch bản yêu thích của bạn, ngôn ngữ này chạy chương trình C ở trên và ghi lại đầu ra của nó. Sử dụng đầu ra đó để tạo tệp tiêu đề C. Ví dụ: nếu bạn đang sử dụng Ruby, nó có thể giống như sau:

sizeof_int = `./sizeof_int`
File.open('include/sizes.h','w') { |f| f.write(<<HEADER) }
/* COMPUTER-GENERATED, DO NOT EDIT BY HAND! */
#define SIZEOF_INT #{sizeof_int}
/* others can go here... */
HEADER

Sau đó, thêm một quy tắc vào Makefile của bạn hoặc tập lệnh xây dựng khác, điều này sẽ làm cho nó chạy tập lệnh trên để xây dựng sizes.h.

Bao gồm sizes.hbất cứ nơi nào bạn cần sử dụng các điều kiện tiền xử lý dựa trên kích thước.

Làm xong!

(Bạn đã bao giờ gõ ./configure && makeđể xây dựng một chương trình chưa? configureVề cơ bản, những tập lệnh nào làm giống như phần trên ...)


điều tương tự khi bạn đang sử dụng các công cụ như "autoconf".
Alexander Stohr

4

Điều gì về macro tiếp theo:

/* 
 * Simple compile time assertion.
 * Example: CT_ASSERT(sizeof foo <= 16, foo_can_not_exceed_16_bytes);
 */
#define CT_ASSERT(exp, message_identifier) \
    struct compile_time_assertion { \
        char message_identifier : 8 + !(exp); \
    }

Ví dụ trong bình luận MSVC nói một cái gì đó như:

test.c(42) : error C2034: 'foo_can_not_exceed_16_bytes' : type of bit field too small for number of bits

1
Đây không phải là câu trả lời cho câu hỏi vì bạn không thể sử dụng điều này trong #ifchỉ thị tiền xử lý.
cmaster - Khôi phục monica

1

Chỉ là tài liệu tham khảo cho cuộc thảo luận này, tôi báo cáo rằng một số trình biên dịch nhận được thời gian xử lý trước sizeof () ar.

Câu trả lời JamesMcNellis là đúng, nhưng một số trình biên dịch gặp phải hạn chế này (điều này có thể vi phạm ansi c nghiêm ngặt).

Về trường hợp này, tôi đề cập đến trình biên dịch IAR C (có lẽ là trình biên dịch hàng đầu cho lập trình vi điều khiển / nhúng chuyên nghiệp).


Bạn có chắc chắn về điều đó không? IAR tuyên bố rằng các trình biên dịch của họ tuân theo các tiêu chuẩn ISO C90 và C99, không cho phép đánh giá sizeoftại thời điểm tiền xử lý. sizeofnên được coi như một định danh.
Keith Thompson

6
Vào năm 1998, một người nào đó trên trang tin comp.std.c đã viết: "Thật tuyệt khi những ngày mà mọi thứ như #if (sizeof(int) == 8)thực sự hoạt động (trên một số trình biên dịch)." Câu trả lời: "Phải có trước thời đại của tôi.", Là từ Dennis Ritchie.
Keith Thompson

Xin lỗi vì đã trả lời muộn ... Vâng, tôi chắc chắn, tôi có các ví dụ làm việc về mã được biên dịch cho vi điều khiển 8/16/32 bit, trình biên dịch Renesas (cả R8 và RX).
grazianovernnatori,

Trên thực tế, nên có một số tùy chọn để yêu cầu ISO C "nghiêm ngặt"
graziano Governatori

nó không phải là vi phạm tiêu chuẩn miễn là tiêu chuẩn không cấm nó. thì tôi sẽ gọi nó là một tính năng hiếm và không theo tiêu chuẩn - do đó bạn sẽ tránh nó trong các trường hợp thông thường vì lợi ích của việc giữ tính độc lập của trình biên dịch và tính di động của nền tảng.
Alexander Stohr

1

#define SIZEOF(x) ((char*)(&(x) + 1) - (char*)&(x)) có thể làm việc


Đây là một giải pháp thú vị tuy nhiên nó chỉ hoạt động với các biến được xác định, không phải với các kiểu. Một giải pháp mà làm việc với loại nhưng không phải với các biến sẽ là:#define SIZEOF_TYPE(x) (((x*)0) + 1)
greydet

7
Nó không hoạt động vì bạn vẫn không thể sử dụng kết quả của nó trong một #ifđiều kiện. Nó không cung cấp lợi ích hơn sizeof(x).
interjay

1

Trong C11 _Static_asserttừ khóa được thêm vào. Nó có thể được sử dụng như:

_Static_assert(sizeof(someThing) == PAGE_SIZE, "Data structure doesn't match page size")

0

Trong mã c ++ di động của tôi ( http://www.starmessagesoftware.com/cpcclibrary/ ) muốn đặt một biện pháp bảo vệ an toàn đối với kích thước của một số cấu trúc hoặc lớp của tôi.

Thay vì tìm cách để bộ tiền xử lý tạo ra lỗi (không thể hoạt động với sizeof () như được nêu ở đây), tôi đã tìm thấy một giải pháp ở đây khiến trình biên dịch gặp lỗi. http://www.barrgroup.com/Embedded-Systems/How-To/C-Fixed-Width-Integers-C99

Tôi đã phải điều chỉnh mã đó để làm cho nó gặp lỗi trong trình biên dịch của tôi (xcode):

static union
{
    char   int8_t_incorrect[sizeof(  int8_t) == 1 ? 1: -1];
    char  uint8_t_incorrect[sizeof( uint8_t) == 1 ? 1: -1];
    char  int16_t_incorrect[sizeof( int16_t) == 2 ? 1: -1];
    char uint16_t_incorrect[sizeof(uint16_t) == 2 ? 1: -1];
    char  int32_t_incorrect[sizeof( int32_t) == 4 ? 1: -1];
    char uint32_t_incorrect[sizeof(uint32_t) == 4 ? 1: -1];
};

2
Bạn có chắc rằng những “−1” đó sẽ không bao giờ được hiểu là 0xFFFF… FF, khiến chương trình của bạn yêu cầu tất cả bộ nhớ có thể định địa chỉ?
Anton Samsonov

0

Sau khi thử macro được đề cập, đoạn này dường như tạo ra kết quả mong muốn ( t.h):

#include <sys/cdefs.h>
#define STATIC_ASSERT(condition) typedef char __CONCAT(_static_assert_, __LINE__)[ (condition) ? 1 : -1]
STATIC_ASSERT(sizeof(int) == 4);
STATIC_ASSERT(sizeof(int) == 42);

Đang chạy cc -E t.h:

# 1 "t.h"
...
# 2 "t.h" 2

typedef char _static_assert_3[ (sizeof(int) == 4) ? 1 : -1];
typedef char _static_assert_4[ (sizeof(int) == 42) ? 1 : -1];

Đang chạy cc -o t.o t.h:

% cc -o t.o t.h
t.h:4:1: error: '_static_assert_4' declared as an array with a negative size
STATIC_ASSERT(sizeof(int) == 42);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
t.h:2:84: note: expanded from macro 'STATIC_ASSERT'
  ...typedef char __CONCAT(_static_assert_, __LINE__)[ (condition) ? 1 : -1]
                                                       ^~~~~~~~~~~~~~~~~~~~
1 error generated.

42 không phải là câu trả lời cho tất cả mọi thứ rốt cuộc ...


0

Để kiểm tra kích thước cấu trúc dữ liệu tại thời điểm biên dịch so với các ràng buộc của chúng, tôi đã sử dụng thủ thuật này.

#if defined(__GNUC__)
{ char c1[sizeof(x)-MAX_SIZEOF_X-1]; } // brakets limit c1's scope
#else
{ char c1[sizeof(x)-MAX_SIZEOF_X]; }   
#endif

Nếu kích thước của x lớn hơn hoặc bằng giới hạn MAX_SIZEOF_X của nó, thì trình duyệt gcc sẽ phàn nàn với lỗi 'kích thước của mảng quá lớn'. VC ++ sẽ đưa ra lỗi C2148 ('tổng kích thước của mảng không được vượt quá 0x7fffffff byte') hoặc C4266 'không thể cấp phát mảng có kích thước không đổi 0'.

Hai định nghĩa này là cần thiết vì gcc sẽ cho phép một mảng có kích thước bằng 0 được xác định theo cách này (sizeof x - n).


-10

Các sizeofnhà điều hành là không có sẵn cho tiền xử lý, nhưng bạn có thể chuyển sizeofđể trình biên dịch và kiểm tra tình trạng này trong thời gian chạy:

#define elem_t double

#define compiler_size(x) sizeof(x)

elem_t n;
if (compiler_size(elem_t) == sizeof(int)) {
    printf("%d",(int)n);
} else {
    printf("%lf",(double)n);
}

13
Nó cải thiện như thế nào đối với câu trả lời đã được chấp nhận? Mục đích xác định compiler_sizephục vụ là gì? Ví dụ của bạn cố gắng thể hiện điều gì?
ugoren
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.