Рассмотрим следующий код ....
#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!