Как реализовать assert(), чтобы использовать статический анализ потока данных оптимизатора gcc?

Рассмотрим следующий код....

#include <stdio.h>

void func( char * a, int i)
{
   printf( "%c\n", a[i]);
}

int main( int argc __attribute__((unused)), char * argv[]  __attribute__((unused)))
{
   int i = 10;
   char a[] = "abc";

   func( a, i);

   return 0;
}

Компилировать с..

   gcc  -W -Wall -Wextra -o b b.c

и никаких предупреждений.

Запустите его, он работает, но печатает мусор.

Компилировать с

   gcc  -O3 -W -Wall -Wextra -o b b.c

И gcc правильно указывает на ошибку....

b.c: In function ‘main’:
b.c:5:21: warning: ‘*((void *)&a+10)’ is used uninitialized in this function [-Wuninitialized]
    printf( "%c\n", a[i]);
                     ^
b.c:11:9: note: ‘a’ was declared here
    char a[] = "abc";

Привет! Это довольно умно для gcc, он проанализировал границы функции! (Я наблюдал на больших проектах, gcc теперь поразительно умен в этом!)

Теперь интуитивно противостоять, добавив утверждения делают вещи хуже!

Рассматривать....

#include <stdlib.h>
#include <stdio.h>
#include <assert.h>

void func( char * a, int i)
{
   assert( i < 4);
   printf( "%c\n", a[i]);
}

int main( int argc __attribute__((unused)), char * argv[]  __attribute__((unused)))
{
   int i = 10;
   char a[] = "abc";

   func( a, i);

   return 0;
}

Компиляция без оптимизации снова не выдает никаких предупреждений, но во время выполнения вы, правильно....

    a: a.c:7: func: Assertion `i < 4' failed.

Compilation aborted (core dumped) at Fri May  4 10:52:26

Но скомпилируйте с...

gcc  -O3 -W -Wall -Wextra -o a a.c

... теперь не выводит предупреждений!

то есть. Хотя gcc знает, что assert сработает во время выполнения... он не может сказать мне больше во время компиляции.

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

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

Какие-либо предложения?

Обновление: вот немного другой вариант....

#include <stdlib.h>
#include <stdio.h>
#include <assert.h>

static char _rangeCheckVariable;
void bunc( int i)
{

   if( __builtin_constant_p( (4))) {
      char __rangeCheck[(4)]="abc";
      _rangeCheckVariable = __rangeCheck[(i)];
   } else {
      assert( (i) < (4));
   }

   printf( "%d\n",i);
}

void func( int i)
{
   bunc( i);
}

int main( int argc __attribute__((unused)), char * argv[]  __attribute__((unused)))
{
   int i = 10;

   func( i);

   return 0;
}

Компилирование с

gcc  -g3 -ggdb3 -O3 -W -Wall -Wextra -c d.c ;objdump -S d.o

Результаты в

d.c: In function ‘main’:
d.c:11:41: warning: array subscript is above array bounds [-Warray-bounds]
       _rangeCheckVariable = __rangeCheck[(i)];
                                         ^

то есть. Вы можете почти полностью отсортировать макрос check_range( i, size), который проверял во время компиляции, что я меньше размера.

Обновление 2: еще страннее.... Следующие компиляции без предупреждений, но.....

#include <stdlib.h>
#include <stdio.h>
#include <assert.h>

static char __rangeCheckVariable;

#define asshurt(exp)                                               \
   do {                                                            \
      char __rangeCheck[2]="a";                                    \
      __rangeCheckVariable =                                       \
         __rangeCheck[(exp) ? 0 : 10];                             \
   } while(0)

void bunc( int i)
{

   asshurt( i< 4);
   assert( i<4);
   printf( "%d\n",i);
}

void func( int i)
{
   bunc( i);
}

int main( int argc __attribute__((unused)), char * argv[]  __attribute__((unused)))
{
   int i = 10;

   func( i);

   return 0;
}

Убрал строку assert( i< 4); и вы получите

gcc  -g3 -ggdb3 -O3 -W -Wall -Wextra -c d.c ;objdump -S d.o
d.c: In function ‘main’:
d.c:11:22: warning: array subscript is above array bounds [-Warray-bounds]
          __rangeCheck[(exp) ? 0 : 10];                             \
                      ^
d.c:17:4: note: in expansion of macro ‘asshurt’
    asshurt( i< 4);
    ^

Обновление 3: еще страннее.

Поиграйте с этим на этом замечательном сайте Godbolt.... Попробуйте переключиться между настройками оптимизации -Os и -O2.

#include <stdlib.h>

void assertFailure( void) __attribute__((warning("Compile time assertion failure")));

int z(void);


int main()
{
   int j;

   for(  j=0;j<4;j++) {
      if( z()) break;
   }

   if( __builtin_constant_p(!(j < 4)))
   {
      if(!(j < 4))
         assertFailure();
   }
   else
      if( !(j < 4))
         abort();

   return 0;
}

На -O2 нет предупреждений (правильно) на -O2 и выше... он неправильно собирается при вызове assertFailure.

Обновление 4: вариант, который работает для всех настроек оптимизации (но не для C++)

#include <stdlib.h>
#include <stdio.h>

void assertFailure( void) __attribute__((warning("Compile time assertion failure")));

static unsigned u;

int z(void) { return u++ % 2u;}

#define assert(exp)                                                 \
   __builtin_choose_expr( __builtin_constant_p(!(exp)),             \
                          ((!(exp)) ? assertFailure() : (void)0),   \
                          ((__builtin_expect( !(exp), 0)) ? abort() : (void)0))

void bunc( char * a, unsigned j)
{
   printf( "%c\n", a[j]);
   // assert( j < 4);
}

void func( char * a)
{
   bunc( a, 5);
}

int main()
{
   int j;
   char a[]="abc";

   func( a);

   for(  j=0;j<4;j++) { 
      if( z()) break;
   } 

   assert( j < 4);

   assert( 4 < 5);

//   assert( 5 < 4);


   return 0;
}

Вы можете играть с ним на Godbolt. Интересная разница в том, что предупреждения gcc-5.1 кажутся лучше, чем 8.1!

1 ответ

Примерно так будет работать в вашем примере:

void assert_warn_abort (void)
  __attribute__ ((warning ("assertion failure"), noreturn));

#define assert(expr)                     \
  ({                                     \
    if (expr)                            \
      (void)0;                           \
    else                                 \
      {                                  \
        if (__builtin_constant_p (expr)) \
          assert_warn_abort ();          \
        else                             \
          abort ();                      \
      }                                  \
  })

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

Другие вопросы по тегам