Подтвердить что ты не робот

C компилятор утверждает - как реализовать?

Я хотел бы реализовать "assert", который предотвращает компиляцию, а не неудачу во время выполнения, в случае ошибки.

В настоящее время у меня есть такой определенный, как этот, который отлично работает, но который увеличивает размер двоичных файлов.

#define MY_COMPILER_ASSERT(EXPRESSION) switch (0) {case 0: case (EXPRESSION):;}

Пример кода (который не удается скомпилировать).

#define DEFINE_A 1
#define DEFINE_B 1
MY_COMPILER_ASSERT(DEFINE_A == DEFINE_B);

Как я могу реализовать это, чтобы он не генерировал никакого кода (чтобы минимизировать размер генерируемых двоичных файлов)?

4b9b3361

Ответ 1

Утверждение времени компиляции в чистом стандарте C возможно, и небольшая часть препроцессорного трюка делает его использование столь же чистым, как использование времени выполнения assert().

Основной трюк заключается в том, чтобы найти конструкцию, которая может быть оценена во время компиляции и может вызвать ошибку для некоторых значений. Один ответ - объявление массива не может иметь отрицательный размер. Использование typedef предотвращает успешное распределение пространства и сохраняет ошибку при сбое.

Само сообщение об ошибке будет критически ссылаться на объявление отрицательного размера (GCC говорит, что "размер массива foo отрицательный" ), поэтому вы должны выбрать имя для типа массива, которое намекает, что эта ошибка действительно является проверкой утверждения.

Еще одна проблема для обработки заключается в том, что только одно имя типа typedef можно указать только один раз в любом компиляторе. Таким образом, макрос должен организовать для каждого использования получение уникального имени типа для объявления.

Моим обычным решением было требование, чтобы макрос имел два параметра. Первое условие является утверждение true, а второе является частью имени типа, объявленного за кулисами. Ответ по плинту намекает на использование вставок в токены и предопределенный макрос __LINE__ для формирования уникального имени, возможно, без лишнего аргумента.

К сожалению, если проверка утверждения находится во включенном файле, он все равно может столкнуться с проверкой с тем же номером строки во втором включенном файле или с номером этой строки в основном исходном файле. Мы могли бы записать это с помощью макроса __FILE__, но он определен как строковая константа, и нет препроцессорного трюка, который может превратить константу строки обратно в часть имени идентификатора; не говоря уже о том, что имена законных файлов могут содержать символы, которые не являются юридическими частями идентификатора.

Итак, я бы предложил следующий фрагмент кода:

/** A compile time assertion check.
 *
 *  Validate at compile time that the predicate is true without
 *  generating code. This can be used at any point in a source file
 *  where typedef is legal.
 *
 *  On success, compilation proceeds normally.
 *
 *  On failure, attempts to typedef an array type of negative size. The
 *  offending line will look like
 *      typedef assertion_failed_file_h_42[-1]
 *  where file is the content of the second parameter which should
 *  typically be related in some obvious way to the containing file
 *  name, 42 is the line number in the file on which the assertion
 *  appears, and -1 is the result of a calculation based on the
 *  predicate failing.
 *
 *  \param predicate The predicate to test. It must evaluate to
 *  something that can be coerced to a normal C boolean.
 *
 *  \param file A sequence of legal identifier characters that should
 *  uniquely identify the source file in which this condition appears.
 */
#define CASSERT(predicate, file) _impl_CASSERT_LINE(predicate,__LINE__,file)

#define _impl_PASTE(a,b) a##b
#define _impl_CASSERT_LINE(predicate, line, file) \
    typedef char _impl_PASTE(assertion_failed_##file##_,line)[2*!!(predicate)-1];

Типичное использование может выглядеть примерно так:

#include "CAssert.h"
...
struct foo { 
    ...  /* 76 bytes of members */
};
CASSERT(sizeof(struct foo) == 76, demo_c);

В GCC ошибка утверждения будет выглядеть так:

$ gcc -c demo.c
demo.c:32: error: size of array `assertion_failed_demo_c_32' is negative
$

Ответ 2

Следующий макрос COMPILER_VERIFY(exp) работает достаточно хорошо.

// combine arguments (after expanding arguments)
#define GLUE(a,b) __GLUE(a,b)
#define __GLUE(a,b) a ## b

#define CVERIFY(expr, msg) typedef char GLUE (compiler_verify_, msg) [(expr) ? (+1) : (-1)]

#define COMPILER_VERIFY(exp) CVERIFY (exp, __LINE__)

Он работает как для C, так и для С++ и может использоваться везде, где допускается typedef. Если выражение истинно, оно генерирует typedef для массива из 1 char (что безвредно). Если выражение ложно, оно генерирует typedef для массива из -1 символов, что обычно приводит к сообщению об ошибке. Выражение, данное как выражение, может быть любым, которое оценивается константой времени компиляции (так что выражение, в котором используется sizeof(), работает отлично). Это делает его гораздо более гибким, чем

#if (expr)
#error
#endif

где вы ограничены выражениями, которые могут быть оценены препроцессором.

Ответ 3

Лучшая запись, которую я могу найти в статических утверждениях в C, находится в pixelbeat. Обратите внимание, что статические утверждения добавляются в С++ 0X и могут входить в C1X, но это не будет какое-то время. Я не знаю, будут ли макросы в моей ссылке увеличить размер ваших двоичных файлов. Я подозреваю, что они не будут, по крайней мере, если вы скомпилируете на разумном уровне оптимизации, но ваш пробег может измениться.

Ответ 4

Если ваш компилятор устанавливает макрос препроцессора, такой как DEBUG или NDEBUG, вы можете сделать что-то вроде этого (иначе вы могли бы установить это в Makefile):

#ifdef DEBUG
#define MY_COMPILER_ASSERT(EXPRESSION)   switch (0) {case 0: case (EXPRESSION):;}
#else
#define MY_COMPILER_ASSERT(EXPRESSION)
#endif

Затем ваш компилятор утверждает только для отладочных сборников.

Ответ 5

Я знаю, что вас интересует C, но посмотрите на повышение С++ static_assert. (Кстати, это, скорее всего, будет доступно в С++ 1x.)

Мы сделали что-то подобное, снова для С++:

#define COMPILER_ASSERT(expr)  enum { ARG_JOIN(CompilerAssertAtLine, __LINE__) = sizeof( char[(expr) ? +1 : -1] ) }

Это работает только на С++. В этой статье обсуждается способ ее изменения для использования в C.

Ответ 6

Когда вы компилируете свои окончательные двоичные файлы, укажите MY_COMPILER_ASSERT как пустое, чтобы его результат не был включен в результат. Определите его так, как вы его используете для отладки.

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

Ответ 7

Как сказал Леандер, статические утверждения добавляются в С++ 11, и теперь они имеют.

static_assert(exp, message)

Например

#include "myfile.hpp"

static_assert(sizeof(MyClass) == 16, "MyClass is not 16 bytes!")

void doStuff(MyClass object) { }

См. страницу cppreference на нем.

Ответ 8

Использование '#error' - это допустимое определение препроцессора, которое заставляет компиляцию останавливаться на большинстве компиляторов. Вы можете просто сделать это так, например, чтобы предотвратить компиляцию при отладке:


#ifdef DEBUG
#error Please don't compile now
#endif

Ответ 9

Ну, вы можете использовать static asserts в библиотеке ускорения.

Я считаю, что они там делают, это определить массив.

 #define MY_COMPILER_ASSERT(EXPRESSION) char x[(EXPRESSION)];

Если EXPRESSION истинно, он определяет char x[1];, что в порядке. Если false, он определяет char x[0];, который является незаконным.