Як я можу використовувати “sizeof” у макросі препроцесора?


95

Чи є спосіб використовувати sizeofмакрос у препроцесорі?

Наприклад, за ці роки було безліч ситуацій, коли я хотів зробити щось на зразок:

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

Точне, що я тут перевіряю, повністю вигадане - суть у тому, що я часто люблю вводити такі типи перевірок під час компіляції (розмір чи вирівнювання), щоб захиститись від того, хто модифікує структуру даних, яка може неправильно вирівняти або змінити розмір речей, які могли б їх зламати.

Само собою зрозуміло - я, мабуть, не можу використовувати a sizeofспособом, описаним вище.


Це точна причина існування систем побудови.
Шимон Тот,

3
Це точна причина, через яку директиви #error завжди повинні бути в подвійних лапках (незмінена константа символів через "ні").
Йенс

1
Привіт @Brad. Будь ласка, подумайте про те, щоб змінити прийняту відповідь на відповідь ніколи, бо тим часом прийнята відповідь трохи застаріла.
Бодо

@BodoThiesen Готово.
Бред

Відповіді:


69

Є кілька способів зробити це. Наступні фрагменти не дадуть коду, якщо він sizeof(someThing)дорівнює PAGE_SIZE; інакше вони видадуть помилку під час компіляції.

1. С11 спосіб

Починаючи з C11, ви можете використовувати static_assert(потрібно #include <assert.h>).

Використання:

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

2. Спеціальний макрос

Якщо ви просто хочете отримати помилку під час компіляції, коли sizeof(something)це не те, що ви очікуєте, ви можете використовувати такий макрос:

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

Використання:

BUILD_BUG_ON( sizeof(someThing) != PAGE_SIZE );

Ця стаття детально пояснює, чому це працює.

3. Специфічні для РС

У компіляторі Microsoft C ++ ви можете використовувати макрос C_ASSERT (потрібно #include <windows.h>), який використовує фокус, подібний до описаного в розділі 2.

Використання:

C_ASSERT(sizeof(someThing) == PAGE_SIZE);

4
... це божевільно. Чому це не прийнята відповідь, @Brad (OP)?
інженер

Гарне посилання на BUILD_BUG_ON.
Петр Вепржек

2
Макрос не працює в GNU gcc(протестовано у версії 4.8.4) (Linux). У ((void)sizeof(...ньому помилки з expected identifier or '(' before 'void'і expected ')' before 'sizeof'. Але в принципі size_t x = (sizeof(...натомість працює за призначенням. Потрібно якось "використати" результат. Щоб дозволити це викликати кілька разів або в межах функції, або в глобальному масштабі, щось на зразок extern char _BUILD_BUG_ON_ [ (sizeof(...) ];можна використовувати неодноразово (без побічних ефектів, фактично _BUILD_BUG_ON_ніде не посилатися ).
JonBrave

Використовуємо статичні твердження набагато довше, ніж 2011 рік.
Дан,

1
@Engineer погляд, маразм припинився;)
Бодо

70

Чи є спосіб використовувати " sizeof" в макросі попереднього процесора?

Ні. Умовні директиви беруть обмежений набір умовних виразів; sizeofце одна з речей, заборонених.

Директиви попередньої обробки обчислюються перед синтаксичним аналізом джерела (принаймні концептуально), тому ще немає типів або змінних, щоб отримати їх розмір.

Однак існують методи отримання тверджень під час компіляції на мові C (наприклад, див. Цю сторінку ).


Чудова стаття - розумне рішення! Хоча вам потрібно адміністратором - вони дійсно розігнали синтаксис C до межі, щоб змусити цей працювати! : -O
Бред

1
Виявляється - як навіть сказано в статті - я зараз будую код ядра Linux - і в ядрі вже є дефініція - BUILD_BUG_ON - де ядро ​​використовує його для таких речей, як: BUILD_BUG_ON (sizeof (char)! = 8)
Brad

2
@Brad BUILD_BUG_ON та інші, що генерують безперечно некоректний код, який не вдасться скомпілювати (і дасть деяке неочевидне повідомлення про помилку в процесі). Насправді це не оператор #if, тому ви не можете, наприклад, виключити блок коду на основі цього.
keltar

10

Я знаю, що це пізня відповідь, але, щоб додати версію Майка, ось версія, яку ми використовуємо, не виділяє жодної пам’яті. Я не придумав перевірку оригінального розміру, я знайшов його в Інтернеті багато років тому, і, на жаль, не можу вказати автора. Два інших - лише продовження тієї самої ідеї.

Оскільки вони є typedef, нічого не виділяється. Якщо в назві __LINE__, це завжди інша назва, тому його можна скопіювати та вставити там, де це потрібно. Це працює в компіляторах MS Visual Studio C та компіляторах GCC Arm. Це не працює в CodeWarrior, CW скаржиться на перевизначення, не використовуючи конструкцію препроцесора __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];

Це насправді добре працює для стандартного проекту С ... мені це подобається!
Ешлі Дункан,

1
Це повинна бути правильна відповідь через нульове розподіл. Ще краще в дефініцію:#define STATIC_ASSERT(condition) typedef char p__LINE__[ (condition) ? 1 : -1];
Рено Серрато

p__LINE__ не створює унікальної назви. Він створює p__LINE__ як змінну. Вам знадобиться попередній макрос і використовувати __CONCAT із sys / cdefs.h.
Coroos

9

Я знаю, що ця тема справді стара, але ...

Моє рішення:

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

Поки цей вираз дорівнює нулю, він добре компілюється. Будь-що інше, і це зривається прямо там. Оскільки змінна є зовнішньою, вона не займе місця, і до тих пір, поки ніхто не посилається на неї (чого не роблять), це не призведе до помилки посилання.

Не такий гнучкий, як макрос assert, але я не зміг змусити це скомпілювати у своїй версії GCC, і це буде ... і я думаю, що він буде компілюватися майже де завгодно.


6
Ніколи не вигадуйте власні макроси, починаючи з двох підкреслень. Цей шлях полягає у божевіллі (вона ж невизначена поведінка ).
Йенс

Є купа прикладів , перерахованих на цій сторінці pixelbeat.org/programming/gcc/static_assert.html
portforwardpodcast

не працює при компіляції з компілятором arm gcc. дає очікувану помилку "помилка: змінено" CHECK "в області файлу"
thunderbird

@Jens Ви маєте рацію, але це буквально не макрос, це декларація змінної. Звичайно, це може заважати макросам.
Мелебій

4

Існуючі відповіді просто показують, як досягти ефекту "тверджень під час компіляції" на основі розміру типу. Це може задовольнити потреби OP у цьому конкретному випадку, але є й інші випадки, коли вам дійсно потрібен попередній процесор, який залежить від розміру типу. Ось як це зробити:

Напишіть собі невелику програму на зразок:

/* 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); }

Складіть це. Напишіть сценарій на вашій улюбленій мові сценаріїв, який запускає вищезазначену програму C та фіксує її результати. Використовуйте цей результат для створення заголовного файлу C. Наприклад, якщо ви використовували Ruby, це може виглядати так:

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

Потім додайте правило до вашого Makefile або іншого сценарію збірки, який змусить його запускати вищезазначений сценарій для збірки sizes.h.

Включайте sizes.hвсюди, де вам потрібно використовувати умови попереднього процесора залежно від розмірів.

Готово!

(Ви коли-небудь вводили текст ./configure && makeдля створення програми? Те, що configureроблять сценарії, в основному точно так само, як і вище ...)


це схоже, коли ви використовуєте такі інструменти, як "autoconf".
Alexander

4

Що щодо наступного макросу:

/* 
 * 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); \
    }

Наприклад, у коментарі MSVC розповідає щось на зразок:

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

1
Це не відповідь на запитання, оскільки ви не можете використовувати це в #ifдирективі препроцесора.
cmaster - відновити моніку

1

Як посилання на це обговорення, я повідомляю, що деякі компілятори отримують sizeof () за час до процесора.

Відповідь JamesMcNellis правильна, але деякі компілятори проходять через це обмеження (це, мабуть, порушує сувору відповідність).

Як випадок цього я маю на увазі компілятор IAR C (мабуть, провідний для професійного мікроконтролера / вбудованого програмування).


Ви впевнені в цьому? IAR стверджує, що їх компілятори відповідають стандартам ISO C90 та C99, які не дозволяють оцінювати sizeofчас попередньої обробки. sizeofслід розглядати як просто ідентифікатор.
Кіт Томпсон,

6
У 1998 році хтось із групи новин comp.std.c написав: "Це було приємно ще в ті часи, коли такі речі, як #if (sizeof(int) == 8)насправді, працювали (на деяких компіляторах)". Відповідь: "Мабуть, це було ще до мого часу", - від Денніса Річі.
Кіт Томпсон,

Вибачте за пізню відповідь ... Так, я впевнений, у мене є робочі приклади коду, складеного для мікроконтролерів 8/16/32 біт, компіляторів Renesas (як R8, так і RX).
graziano guvernatori

Насправді, повинен бути якийсь варіант вимагати "суворого" ISO C
граціано-губернатори

це не є порушенням стандарту, якщо стандарт не забороняє цього. тоді я б назвав це рідкісною та нестандартною функцією - таким чином, ви уникатимете її в звичайних випадках заради збереження незалежності компілятора та портативності платформи.
Alexander

1

#define SIZEOF(x) ((char*)(&(x) + 1) - (char*)&(x)) може спрацювати


Це цікаве рішення, однак воно працює лише з визначеними змінними, а не з типами. Іншим рішенням, яке працює з типом, але не зі змінними, буде:#define SIZEOF_TYPE(x) (((x*)0) + 1)
greydet

7
Це не працює, тому що ви все ще не можете використовувати його результат в #ifумовах. Це не забезпечує жодної вигоди sizeof(x).
інтержой

1

У C11 _Static_assertдодано ключове слово. Його можна використовувати, як:

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

0

У своєму портативному коді c ++ ( http://www.starmessagesoftware.com/cpcclibrary/ ) я хотів забезпечити безпеку розмірів деяких моїх конструкцій або класів.

Замість того, щоб знайти спосіб для препроцесора видавати помилку (яка не може працювати з sizeof (), як зазначено тут), я знайшов тут рішення, яке змушує компілятор видавати помилку. http://www.barrgroup.com/Embedded-Systems/How-To/C-Fixed-Width-Integers-C99

Мені довелося адаптувати цей код, щоб він видавав помилку в моєму компіляторі (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
Ви впевнені, що ці «-1» ніколи не будуть інтерпретовані як 0xFFFF… FF, внаслідок чого ваша програма запитуватиме всю адресну пам’ять?
Антон Самсонов

0

Після випробування згаданих макросів цей фрагмент, здається, дає бажаний результат ( 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);

Біг 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];

Біг 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 все-таки не відповідь на все ...


0

Щоб перевірити під час компіляції розмір структур даних щодо їх обмежень, я використав цей фокус.

#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

Якщо розмір x більше або дорівнює обмеженню MAX_SIZEOF_X, тоді gcc скаржиться з помилкою "розмір масиву занадто великий". VC ++ видасть або помилку C2148 ('загальний розмір масиву не повинен перевищувати 0x7fffffff байт'), або C4266 'не може виділити масив постійного розміру 0'.

Два визначення необхідні, оскільки gcc дозволить визначати масив нульового розміру таким чином (sizeof x - n).


-10

sizeofОператор не доступний для препроцесора, але ви можете передати sizeofкомпілятору і перевірити стан під час виконання:

#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
Як це покращує вже прийняту відповідь? Якій меті compiler_sizeслужить визначення ? Що намагається показати ваш приклад?
угорен
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.