Как реализовать assert (), чтобы использовать статический анализ потока данных оптимизатора gcc? - PullRequest
0 голосов
/ 04 мая 2018

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

#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;
}

В -O нет предупреждений (правильно) для -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 Ответ

0 голосов
/ 05 мая 2018

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

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 ();                      \
      }                                  \
  })

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

...