Возможно утверждение времени компиляции в чистом стандартном 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
$