_Static_assert замена, чтобы показать значение в C - PullRequest
0 голосов
/ 15 ноября 2018

Возможно ли, чтобы диагностический вывод ошибки / предупреждения компилятора вычислял числовое значение во время компиляции в C11 или C17 (т.е. не используя шаблоны)? Ссылка ниже делает это в C ++, используя шаблон магии. Намерение состоит в том, чтобы использовать это как замену _Static_assert, которая печатает значения неравного невыполненного выражения. В идеале, оно должно быть в состоянии оценить выражение как истинное или ложное и печатать только тогда, когда оно не проходит оценку.

Это явно зависит от компилятора, поэтому предположим, что GCC.

Отображение целого числа во время компиляции в static_assert ()

Ответы [ 3 ]

0 голосов
/ 19 ноября 2018

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

#define STRINGIFY(x) #x
#define STR(x) STRINGIFY(x)
...
STR(123)

В частности, для оператора sizeof он становится сложнее, так как вычисляется позже, чем когда расширение макроса происходит впрепроцессор.Поскольку C11 доступен, вы могли бы вместо этого использовать _Generic.

Вы можете создать временный составной литерал с размером структуры, а затем _Generic сравнить указатель на созданный тип с указателем надругой массив ожидаемого размера.

Например, мы можем создать составной литерал (char[sizeof(type)]){0}, где тип char на самом деле не имеет значения, а затем взять адрес этого, &(char[sizeof(type)]){0}.Сравните этот тип с указателем массива на массив ожидаемого размера:

_Generic( &(char[sizeof(type)]){0}, 
          char(*)[expected] : true )

Полный пример:

#include <stdbool.h>

#define static_size_assert(type, expected) \
  _Generic( &(char[sizeof(type)]){0}, char(*)[expected] : true)


int main (void)
{
  typedef struct { char s[3]; int i; } foo_t;

  if(static_size_assert(foo_t, 7))
  {
    // ...
  }
  return 0;
}

В случае ожидаемого заполнения структуры это приведет к стандартномусовместимый компилятор для создания сообщения об ошибке типа (от gcc):

error: «_Generic» селектор типа «char (*) [8]» не совместим ни с какой ассоциацией

, тогда как static_size_assert(foo_t, 8) скомпилируется чисто и вернет true.Он работает до тех пор, пока переданное число является целочисленной константой времени компиляции, а не переменной.

0 голосов
/ 21 ноября 2018

Основываясь на ответах Лундина и Тавиана, общий макрос решения для положительных и отрицательных значений выглядит следующим образом:

#define STATIC_ASSERT_EQUAL(VALUE, EXPECTED)                                   \
  (void)_Generic(&(char[(EXPECTED) > 0 ? (VALUE) : (EXPECTED) < 0 ?            \
                        -(VALUE) : (VALUE) == 0 ? 0x7FFFFFFF : (VALUE)]){0},   \
                 char(*)[(EXPECTED) > 0 ? (EXPECTED) : (EXPECTED) < 0 ?        \
                         -(EXPECTED) : 0x7FFFFFFF] : 0)

Макрос должен использоваться внутри функции.Идея состоит в том, что EXPECTED - это значение, известное программисту, а VALUE - неизвестное вычисленное значение.Это было проверено с GCC 6.1.

Эти проходы без ошибок:

STATIC_ASSERT_EQUAL(-1, -1);
STATIC_ASSERT_EQUAL(0, 0);
STATIC_ASSERT_EQUAL(1, 1);

Однако это также, к сожалению, проходит без ошибок, потому что 0 псевдонимов с 0x7FFFFFFF:

STATIC_ASSERT_EQUAL(0x7FFFFFFF, 0);

Ошибка отображается следующим образом:

STATIC_ASSERT_EQUAL(2, 1);
error: ‘_Generic’ selector of type ‘char (*)[2]’ is not compatible with any association

Если у вас включены определенные предупреждения:

STATIC_ASSERT_EQUAL(0, 1);
error: ISO C forbids zero-size array [-Werror=pedantic]

Поскольку значение EXPECTED не равно 0, программист может принять VALUE == 0.

STATIC_ASSERT_EQUAL(-2, 1);
error: size of unnamed array is negative

В этом случае значение не отображается, но программист может видеть, что ЗНАЧЕНИЕ отрицательно, когда этого не должно быть, поэтому отрицайте ЗНАЧЕНИЕ, и оно отобразится.

STATIC_ASSERT_EQUAL(2, -1);
error: size of unnamed array is negative

Аналогично вышесказанному, программист знает, что ЗНАЧЕНИЕ должно быть отрицательным, но это не так, поэтому отрицание ЗНАЧЕНИЯ заставит его отображаться.

STATIC_ASSERT_EQUAL(-2, -1);
error: ‘_Generic’ selector of type ‘char (*)[2]’ is not compatible with any association

Программист знает, что оно должно быть отрицательным, и здесь он отображает положительный эквивалентVALUE.

STATIC_ASSERT_EQUAL(2, 0);
error: ‘_Generic’ selector of type ‘char (*)[2]’ is not compatible with any association

Вышеуказанный нулевой регистр работает как положено.

0 голосов
/ 15 ноября 2018

Это удивительно сложно заставить GCC сделать. Я нашел этот ответ: https://stackoverflow.com/a/35261673/502399, который предполагает что-то вроде этого:

void error() {
    int array[sizeof(struct X)];
    __builtin_printf("%d", &array);
}

который выводит что-то вроде

foo.c: In function ‘error’:
foo.c:8:21: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘int (*)[8]’ [-Wformat=]                      

  __builtin_printf("%d", &array);
                    ~^   ~~~~~~

до тех пор, пока вы можете передать -Wformat или -Wall или что-то.

Чтобы посмотреть, есть ли более простой способ, я нашел источник GCC для этого сообщения и обнаружил, что тип аргумента был напечатан со специальной строкой формата %qT, специфичной для GCC, поэтому я искал другие варианты использования этой строки. В частности, я искал его использование в ошибках, а не в предупреждениях, чтобы оно работало независимо от флагов предупреждений. Я нашел использование в binary_op_error(), из которого я сделал этот пример:

int array[sizeof(struct X)];
int error = 1 / &array;

, который производит

foo.c:7:15: error: invalid operands to binary / (have ‘int’ and ‘int (*)[8]’)
 int error = 1 / &array;
               ^ ~~~~~~

другие возможности включают

int array[sizeof(struct X)];
int error = __sync_fetch_and_add(&array, 1);

и

int error = _Generic((int (*)[sizeof(struct X)])0, int: 0);

и

int foo(double bar);
int error = foo((int (*)[sizeof(struct X)])0);

и т.д.

...