snprintf и слишком умный компилятор
Наткнулся на забавное. При попытке скомпилировать достаточно тривиальный код:
gcc -Wall -Wextra -pedantic -D_FORTIFY_SOURCE=2 -O2 -o test test.c
...получаем две предупреждения от компилятора:
test.c: In function ‘main’:
test.c:22:74: warning: ‘%08X’ directive output may be truncated writing 8 bytes into a region of size between 7 and 85 [-Wformat-truncation=]
snprintf(buf, sizeof(buf), "event=match name=%s addr=%s stag=%08X ftag=%08X score=%d",
^~~~
test.c:22:30: note: directive argument in the range [-32768, 32767]
snprintf(buf, sizeof(buf), "event=match name=%s addr=%s stag=%08X ftag=%08X score=%d",
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /usr/include/stdio.h:873,
from test.c:1:
/usr/include/x86_64-linux-gnu/bits/stdio2.h:67:10: note: ‘__builtin___snprintf_chk’ output between 60 and 143 bytes into a destination of size 128
return __builtin___snprintf_chk (__s, __n, __USE_FORTIFY_LEVEL - 1,
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
__bos (__s), __fmt, __va_arg_pack ());
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Конкретная строка, которая вызывает ошибку:
snprintf(buf, sizeof(buf), "event=match name=%s addr=%s stag=%08X ftag=%08X score=%d",
name, addr, match.stag, match.ftag, match.score);
Ну казалось бы: обычное дело, не хватает размера буфера. Однако посмотрите внимательно на 3-5 строки. Дело в том, что ftag (четвёртый параметр) - это uint32_t, но компилятор его опознаёт как "directive argument in the range [-32768, 32767]", т.е. int16_t. Такой тип имеет среди параметров только пятый - score.
Получается, что в вызове snprintf() компилятор по какой-то причине выкидывает четвёртый параметр, и пытается применить к следующему по списку (score) непредназначенный для него спецификатор (%08X), указанный в шаблоне форматирования. Это довольно неожиданное поведение для snprintf(), поскольку она специально предназначена записи в буфер известного размера, и в спецификации указано именно что "урезание" (truncate) получившейся строки до размера буфера при недостатке места, но никак не изменение порядка параметров в самой функции.
Это очевидно вызвано "переоптимизацией" компилятора, поскольку проблема пропадает при любом из следующих действий:
- удаления из флагов препроцессора -D_FORTIFY_SOURCE=2
- уменьшения уровня оптимизации до -O0
- ну и разумеется, увеличения размера буфера
...хотя у меня нет уверенности, что пропадает сама проблема, а не только предупреждение о ней.
В любом случае, включение дополнительных проверок компилятора,
не полагаясь на дефолтный -Wall
- это хорошая идея и значительная экономия времени
на отладку уже в рантайме.