Статическое утверждение в C



каков наилучший способ достижения времени компиляции статических утверждений в C (не C++), с особым акцентом на GCC?

723   10  

10 ответов:

стандарт C11 добавляет _Static_assert ключевое слово.

Это реализовано с gcc-4.6:

_Static_assert (0, "assert1"); /* { dg-error "static assertion failed: \"assert1\"" } */

первый слот должен быть неотъемлемой константное выражение. Второй слот-это постоянный строковый литерал, который может быть длинным (_Static_assert(0, L"assertion of doom!")).

Я должен отметить, что это также реализовано в последних версиях лязг.

это работает в функциональной и нефункциональной области (но не внутри структур, союзов).

#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(COND)?1:-1]

STATIC_ASSERT(1,this_should_be_true); 

int main()
{
 STATIC_ASSERT(1,this_should_be_true); 
}
  1. если утверждение времени компиляции не может быть сопоставлено, то почти понятное сообщение генерируется GCC sas.c:4: error: size of array ‘static_assertion_this_should_be_true’ is negative

  2. макрос может или должен быть изменен для создания уникального имени для typedef (т. е. concatenate __LINE__ в конце static_assert_... наименование)

  3. вместо троичного, это может быть используется также #define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[2*(!!(COND))-1] который работает даже на ржавом olde cc65 (для процессора 6502) компилятор.

обновление: Для полноты картины, вот версия с __LINE__

#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(!!(COND))*2-1]
// token pasting madness:
#define COMPILE_TIME_ASSERT3(X,L) STATIC_ASSERT(X,static_assertion_at_line_##L)
#define COMPILE_TIME_ASSERT2(X,L) COMPILE_TIME_ASSERT3(X,L)
#define COMPILE_TIME_ASSERT(X)    COMPILE_TIME_ASSERT2(X,__LINE__)

COMPILE_TIME_ASSERT(sizeof(long)==8); 
int main()
{
    COMPILE_TIME_ASSERT(sizeof(int)==4); 
}

UPDATE2: GCC specific code

GCC 4.3 (я думаю) ввел атрибуты функции "ошибка" и "предупреждение". Если вызов функции с этим атрибутом не может быть устранен путем устранения мертвого кода (или другого меры) затем генерируется ошибка или предупреждение. Это может быть использовано, чтобы сделать время компиляции утверждает с пользовательскими описаниями сбоев. Остается определить, как их можно использовать в области пространства имен, не прибегая к фиктивной функции:

#define CTC(X) ({ extern int __attribute__((error("assertion failure: '" #X "' not true"))) compile_time_check(); ((X)?0:compile_time_check()),0; })

// never to be called.    
static void my_constraints()
{
CTC(sizeof(long)==8); 
CTC(sizeof(int)==4); 
}

int main()
{
}

и вот как это выглядит:

$ gcc-mp-4.5 -m32 sas.c 
sas.c: In function 'myc':
sas.c:7:1: error: call to 'compile_time_check' declared with attribute error: assertion failure: `sizeof(int)==4` not true

cl

Я знаю, что вопрос явно упоминает gcc, но только для полноты здесь есть настройка для компиляторов Microsoft.

использование отрицательного размера массива typedef не убеждает cl выплюнуть приличную ошибку. Он просто говорит:error C2118: negative subscript. Битовое поле нулевой ширины в этом отношении лучше. Поскольку это включает в себя typedeffing структуру, нам действительно нужно использовать уникальные имена типов. __LINE__ не режет горчицу - можно иметь COMPILE_TIME_ASSERT() on одна и та же строка в заголовке и исходном файле, и ваша компиляция сломается. __COUNTER__ приходит на помощь (и это было в gcc с 4.3).

#define CTASTR2(pre,post) pre ## post
#define CTASTR(pre,post) CTASTR2(pre,post)
#define STATIC_ASSERT(cond,msg) \
    typedef struct { int CTASTR(static_assertion_failed_,msg) : !!(cond); } \
        CTASTR(static_assertion_failed_,__COUNTER__)

теперь

STATIC_ASSERT(sizeof(long)==7, use_another_compiler_luke)

под cl выдает:

ошибка C2149: 'static_assertion_failed_use_another_compiler_luke': именованное битовое поле не может иметь нулевой ширины

Gcc также дает понятное сообщение:

ошибка: нулевая ширина для битового поля 'static_assertion_failed_use_another_compiler_luke'

С Википедия:

#define COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;}

COMPILE_TIME_ASSERT( BOOLEAN CONDITION );

при использовании макроса STATIC_ASSERT () с __LINE__, можно избежать столкновения номера строки между записью в a .c-файл и другая запись в заголовочном файле, включая __INCLUDE_LEVEL__.

например :

/* Trickery to create a unique variable name */
#define BOOST_JOIN( X, Y )      BOOST_DO_JOIN( X, Y )
#define BOOST_DO_JOIN( X, Y )   BOOST_DO_JOIN2( X, Y )
#define BOOST_DO_JOIN2( X, Y )  X##Y
#define STATIC_ASSERT(x)        typedef char \
        BOOST_JOIN( BOOST_JOIN(level_,__INCLUDE_LEVEL__), \
                    BOOST_JOIN(_assert_on_line_,__LINE__) ) [(x) ? 1 : -1]

классический способ - это использование массива:

char int_is_4_bytes_assertion[sizeof(int) == 4 ? 1 : -1];

это работает, потому что если утверждение истинно, массив имеет размер 1 и он действителен, но если он ложен, размер -1 дает ошибку компиляции.

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

для тех из вас, кто хочет что-то действительно основное и портативное, но не имеет доступа к функциям C++11, я написал именно это.
Используйте STATIC_ASSERT обычно (вы можете написать его дважды в той же функции, если хотите) и использовать GLOBAL_STATIC_ASSERT вне функций с уникальной фразой в качестве первого параметра.

#if defined(static_assert)
#   define STATIC_ASSERT static_assert
#   define GLOBAL_STATIC_ASSERT(a, b, c) static_assert(b, c)
#else
#   define STATIC_ASSERT(pred, explanation); {char assert[1/(pred)];(void)assert;}
#   define GLOBAL_STATIC_ASSERT(unique, pred, explanation); namespace ASSERTATION {char unique[1/(pred)];}
#endif

GLOBAL_STATIC_ASSERT(first, 1, "Hi");
GLOBAL_STATIC_ASSERT(second, 1, "Hi");

int main(int c, char** v) {
    (void)c; (void)v;
    STATIC_ASSERT(1 > 0, "yo");
    STATIC_ASSERT(1 > 0, "yo");
//    STATIC_ASSERT(1 > 2, "yo"); //would compile until you uncomment this one
    return 0;
}

объяснение:
Сначала он проверяет, есть ли у вас реальное утверждение, которое вы определенно хотели бы использовать, если это доступный.
Если вы этого не сделаете, он утверждает, получая ваш predicate, и разделив его на себя. Это делает две вещи.
Если это ноль, id est, утверждение не удалось, это вызовет ошибку деления на ноль (арифметика вынуждена, потому что она пытается объявить массив).
Если он не равен нулю, он нормализует размер массива до 1. Поэтому, если утверждение прошло, вы все равно не хотите, чтобы оно провалилось, потому что ваш предикат оценивается в -1 (недействительным), или 232442 (массивная трата пространства, IDK, если она будет оптимизирована).
Ибо STATIC_ASSERT он заключен в фигурные скобки, это делает его блоком, который охватывает переменную assert, то есть вы можете написать его много раз.
Он также бросает его в void, который является известным способом избавления от unused variable предупреждения.
Ибо GLOBAL_STATIC_ASSERT, вместо того, чтобы быть в блоке кода, он генерирует пространство имен. Пространства имен разрешены вне функций. А unique идентификатор необходим, чтобы остановить любые конфликтующие определения, если вы используете это несколько раз.


работал для меня на GCC и VS ' 12 C++

это работает, с "удалить неиспользуемые" установить. Я могу использовать одну глобальную функцию для проверки глобальных параметров.

//
#ifndef __sassert_h__
#define __sassert_h__

#define _cat(x, y) x##y

#define _sassert(exp, ln) \
extern void _cat(ASSERT_WARNING_, ln)(void); \
if(!(exp)) \
{ \
    _cat(ASSERT_WARNING_, ln)(); \
}

#define sassert(exp) _sassert(exp, __LINE__)

#endif //__sassert_h__

//-----------------------------------------
static bool tab_req_set_relay(char *p_packet)
{
    sassert(TXB_TX_PKT_SIZE < 3000000);
    sassert(TXB_TX_PKT_SIZE >= 3000000);
    ...
}

//-----------------------------------------
Building target: ntank_app.elf
Invoking: Cross ARM C Linker
arm-none-eabi-gcc ...
../Sources/host_if/tab_if.c:637: undefined reference to `ASSERT_WARNING_637'
collect2: error: ld returned 1 exit status
make: *** [ntank_app.elf] Error 1
//

это работало для некоторых старых gcc. Извините, что я забыл, какая это была версия:

#define _cat(x, y) x##y

#define _sassert(exp, ln)\
extern char _cat(SASSERT_, ln)[1]; \
extern char _cat(SASSERT_, ln)[exp ? 1 : 2]

#define sassert(exp) _sassert((exp), __LINE__)

//
sassert(1 == 2);

//
#148 declaration is incompatible with "char SASSERT_134[1]" (declared at line 134)  main.c  /test/source/controller line 134    C/C++ Problem

Я не рекомендуем использовать решение с помощью typedef:

#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(COND)?1:-1]

объявление массива с typedef ключевое слово не гарантируется для оценки во время компиляции. Например, следующий код в области блока будет компилироваться:

int invalid_value = 0;
STATIC_ASSERT(invalid_value, this_should_fail_at_compile_time_but_will_not);

Я бы рекомендовал это вместо этого (на C99):

#define STATIC_ASSERT(COND,MSG) static int static_assertion_##MSG[(COND)?1:-1]

из-за static ключевое слово, массив будет определен во время компиляции. Обратите внимание, что это утверждение будет работать только с COND которые оцениваются во время компиляции. Он не будет работать (т. е. компиляция завершится неудачно) с условиями, которые основаны на значениях в памяти, таких как значения, присвоенные переменным.

Comments

    Ничего не найдено.