Comments 14
Не понимаю, почему просто не взять и не написать код, где значение берётся из переменной bias.
Возможно автор просто слепо надеется что в памяти bias ВСЕГДА будет лежать сразу после long m[9]; и это пока так и есть, раз все у всех работает. Но вообще да, это жуткий, жуткий и ненадежный код, который надеется на непонятно что - вдруг когда-то в будущем изменится стандарт и оптимизатор например, будет эту структуру в памяти размещать по другому.
Тут явно не хватает примера с массивом нулевого размера в конце struct :)
Когда-то это было мэйнстримом. Например, при работе с IBM DB/2 структуры с массивом нулевого (или единичного, но вроде все же нулевого, немного запамятовал) размера в конце были сплошь и рядом. Интересно, они отошли от такой практики?
Программный интерфейс DB2 вырос из языка PL/I, где существует много средств для управления размещением переменных в памяти и интерпретацией выхода индекса за границу массива, поэтому в PL/I (как и в ассемблере) такие действия обычно не являются случаем неопределённого поведения. В отличие от языка Си.
В частности, язык PL/I гарантировал бы, что в случае, подобном приведённому автором, обращения к m(9) и bias давали бы одинаковый результат (при выключенном контроле выхода за границы массива, что происходит по умолчанию). Но всё равно писать настолько неряшливо нет никаких причин. А в случае компилятора Си нет и никаких гарантий, что выравнивание поля bias совпадёт с выравниванием элементов массива m.
Flexible array member входит в стандарт C99. Но там не нулевого размера, а неполного типа(incomplete type), т.е. вида int a[];
.
К п.37. Пусть пользователь угадывает, в каком порядке надо подключать заголовочные файлы (да, windows.h, я смотрю на тебя).
Кстати, есть идея для предупреждения, но вот только не могу формализовать условие, чтобы отсечь ложноположительные срабатывания.
Если на гитхабе сделать поиск по FLT_MIN (1e-38), то видно, что в каждом десятом случае её пытаются по ошибке использовать вместо -FLT_MAX, особенно заметно при инициализации граничных здачений для всяких bounding box.
Это смешно, пока ты не понимаешь что жирный текст это описание твоего кода)
N 39
Я однажды словил баг в своём старом коде в функции, принимающей переменное число аргументов. Код был написан во времена, когда я был юн и неопытен, и про va_arg() не слышал.
Так вот, этот код, использующий адресную арифметику, служил верой и правдой на x86, и даже на x86_64 и ARM. Но на Windows ARM64 упал. Заработал после замены на va_start, va_arg и т.д.
#include <windows.h>
int AddItem(HWND hDlg, int itemId, LPCTSTR item) {
return ::SendDlgItemMessage(hDlg, itemId, CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(item));
}
bool AddItems(HWND hDlg, int itemId, int itemCount, LPCTSTR item, ...) {
bool result = true;
for (int i = 0; i < itemCount; i++) {
if (AddItem(hDlg, itemId, *(&item + i)) < 0)
result = false;
}
return result;
}
Я проверял проект с помощью PVS-Studio и он ничего не сказал плохого.
N 40
Запись за границу массива – это неопределённое поведение.
В Win32 API часто встречаются структуры с массивом единичного размера в конце. Запись и чтение этих массивов тоже UB?
ИМХО, здесь лучше variadic template использовать.
Так вот, этот код, использующий адресную арифметику, служил верой и правдой на x86, и даже на x86_64 и ARM. Но на Windows ARM64 упал. Заработал после замены на va_start, va_arg и т.д.
Код рассчитан на RTL-размещение элементов в стеке начиная с item с размером элементов, равным длине указателя. Надо копать документацию MSVC ABI ARM64, чтобы точно понять, что там происходит. В общем случае UB, зависимое от реализации(implementation-defined).
Например, item может передаваться в регистре, а не на стеке. Другой вариант - выравнивание переменных аргументов может не быть равным размеру указателя(что вряд ли, но в теории возможно на каких-нибудь экзотичных архитектурах).
В Win32 API часто встречаются структуры с массивом единичного размера в конце. Запись и чтение этих массивов тоже UB?
Приведённая структура это обычная структура со счётчиком и массивом из одного элемента, что вообще соответствует всем стандартам. В отличие от массива нулевого размера(в GNU C это расширение стандарта, в общем случае зависит от реализации) или неполного типа(С99).
Код, который с ней работает, может быть или не быть UB. Если правильно выделена память под количество элементов размеромsizeof(FILEGROUPDESCRIPTORW) + sizeof(FILEDESCRIPTORW)*(cItems - 1)
и за границу при обращении не выходят, то на мой взгляд тут нет UB.
За пример с va_arg
спасибо. Выписал в список идей для новых диагностик.
По поводу массивов единичного размера в конце структур. Да, это особая тема, про которую я забыл упомянуть. Кстати, этот момент не очень понятно, как описать. Ибо всё будет работать, так как компиляторы точно в курсе такого приёма создания структур переменного размера. Другое дело, я не уверен, как всё это смотрится с теоретической точки зрения.... :)
60 антипаттернов для С++ программиста, часть 8 (совет 36 — 40)