Как я могу использовать sizeof в макросе препроцессора?

95

Есть ли способ использовать a sizeofв макросе препроцессора?

Например, за эти годы было множество ситуаций, в которых я хотел сделать что-то вроде:

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

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

Излишне говорить - похоже, что я не могу использовать a sizeofописанным выше способом.

Брэд
источник
Это точная причина, по которой существуют системы сборки.
Шимон Тот
3
Это точная причина, по которой директивы #error всегда должны заключаться в двойные кавычки (незавершенная символьная константа из-за «не»).
Йенс
1
Привет @Brad. Пожалуйста, подумайте об изменении принятого ответа на ответ nevermind, потому что тем временем принятый в настоящее время ответ немного устарел.
Бодо Тизен
@BodoThiesen Готово.
Брэд

Ответы:

69

Есть несколько способов сделать это. Следующие ниже фрагменты кода не будут создавать кода, если они sizeof(someThing)равны PAGE_SIZE; в противном случае они вызовут ошибку времени компиляции.

1. Способ C11

Начиная с 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. Специфичные для MS

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

Использование:

C_ASSERT(sizeof(someThing) == PAGE_SIZE);
неважно
источник
4
...это безумие. Почему это не принятый ответ, @Brad (OP)?
Инженер
Хорошая ссылка на BUILD_BUG_ON.
Petr Vepřek
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 02
Статические утверждения используются гораздо дольше, чем в 2011 году.
Дэн
1
@Engineer look, безумие прекратилось;)
Бодо Тисен
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];
Павел
источник
Это действительно хорошо работает для стандартного проекта C ... Мне это нравится!
Эшли Дункан,
1
Это должен быть правильный ответ из-за нулевого распределения. Еще лучше определиться:#define STATIC_ASSERT(condition) typedef char p__LINE__[ (condition) ? 1 : -1];
Renaud Cerrato
p__LINE__ не создает уникального имени. Он производит p__LINE__ как переменную. Вам понадобится макрос препроцессора и используйте __CONCAT из sys / cdefs.h.
Coroos
9

Я знаю, что эта ветка действительно старая, но ...

Мое решение:

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

Пока это выражение равно нулю, оно компилируется нормально. Что-нибудь еще, и оно тут же взорвется. Поскольку переменная extern'd, она не будет занимать места, и пока на нее никто не ссылается (а они не будут), это не вызовет ошибку ссылки.

Не такой гибкий, как макрос assert, но я не смог заставить его скомпилировать в моей версии GCC, и это будет ... и я думаю, что он будет компилироваться где угодно.

Скотт
источник
6
Никогда не изобретайте собственные макросы, начинающиеся с двух знаков подчеркивания. Этот путь лежит безумие (также известное как неопределенное поведение ).
Йенс
На этой странице приведено
portforwardpodcast
не работает при компиляции с помощью компилятора arm gcc. выдает ожидаемую ошибку «ошибка: переменно измененный ' CHECK ' в области файла»
thunderbird
@Jens Вы правы, но это буквально не макрос, это объявление переменной. Конечно, это может мешать макросам.
Melebius
4

Существующие ответы просто показывают, как добиться эффекта «утверждений времени компиляции» в зависимости от размера типа. Это может удовлетворить потребности OP в данном конкретном случае, но есть и другие случаи, когда вам действительно нужен условный препроцессор, основанный на размере типа. Вот как это сделать:

Напишите себе небольшую программу на C, например:

/* 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делают скрипты, в основном такие же, как и выше ...)

Alex D
источник
это то же самое, когда вы используете такие инструменты, как "autoconf".
Александр
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 () ar.

Ответ Джеймса МакНеллиса правильный, но некоторые компиляторы проходят через это ограничение (это, вероятно, нарушает строгий ответ).

В качестве примера я имею в виду C-компилятор IAR (вероятно, ведущий для профессионального программирования микроконтроллеров / встроенных программ).

Graziano Governatori
источник
Вы уверены, что? IAR утверждает, что их компиляторы соответствуют стандартам ISO C90 и C99, которые не позволяют выполнять оценку sizeofво время предварительной обработки. sizeofследует рассматривать как просто идентификатор.
Кейт Томпсон,
6
В 1998 году кто-то в группе новостей comp.std.c написал: «Это было хорошо в те дни, когда такие вещи, как на #if (sizeof(int) == 8)самом деле, работали (на некоторых компиляторах)». Ответ: «Должно быть, было до меня», - от Денниса Ричи.
Кейт Томпсон
Извините за поздний ответ ... Да, я уверен, у меня есть рабочие примеры кода, скомпилированного для 8/16/32 битных микроконтроллеров, компиляторов Renesas (как R8, так и RX).
Graziano Governatori
На самом деле, должна быть какая-то возможность требовать «строгого» стандарта ISO C
graziano Governatori
это не нарушение стандарта, если стандарт не запрещает это. тогда я бы назвал это редкой и нестандартной функцией - поэтому вы будете избегать ее в обычных случаях ради сохранения независимости компилятора и переносимости платформы.
Александр
1

#define SIZEOF(x) ((char*)(&(x) + 1) - (char*)&(x)) может работать


источник
Это интересное решение, однако оно работает только с определенными переменными, а не с типами. Другое решение, которое работает с типом, но не с переменными, было бы:#define SIZEOF_TYPE(x) (((x*)0) + 1)
greydet 06
7
Это не работает, потому что вы по-прежнему не можете использовать его результат в #ifусловии. Это не дает никакой выгоды sizeof(x).
Interjay
1

В C11 _Static_assertдобавлено ключевое слово. Его можно использовать как:

_Static_assert(sizeof(someThing) == PAGE_SIZE, "Data structure doesn't match page size")
Cagatayo
источник
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 - это не ответ на все вопросы ...

Coroos
источник
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);
}
Freeman
источник
13
Чем он лучше уже принятого ответа? Какой цели compiler_sizeслужит определение ? Что ваш пример пытается показать?
ugoren