Pull to refresh

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.

https://github.com/PacktPublishing/Mastering-Graphics-Programming-with-Vulkan/blob/2ad4e94a0e003d37dd3dbef46cc033a483f133d6/source/chapter7/graphics/gltf_scene.cpp#L310

https://github.com/spaceyjase/godot/blob/be63fdff8031bf5fdb0b068cb10b22566f98f7d0/servers/rendering/renderer_scene_occlusion_cull.h#L81

https://github.com/JimmieKJ/unrealTournament/blob/cac883d9143e72d21c046bfa4a0915b3ad533dad/Engine/Source/Runtime/Engine/Private/LocalPlayer.cpp#L830

Это смешно, пока ты не понимаешь что жирный текст это описание твоего кода)

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 использовать.

Как-то так:

template<typename... T>
void AddItems(HWND hDlg, int itemId, const T&... args) {
for (auto&& item : std::initializer_list{ args... }) {
AddItem(hDlg, itemId, item);
}

Пример вызова:

AddItems(hDlg, itemId, _T("Item 1"), _T("Item 2"), _T("Item3"));

Так вот, этот код, использующий адресную арифметику, служил верой и правдой на 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 спасибо. Выписал в список идей для новых диагностик.

По поводу массивов единичного размера в конце структур. Да, это особая тема, про которую я забыл упомянуть. Кстати, этот момент не очень понятно, как описать. Ибо всё будет работать, так как компиляторы точно в курсе такого приёма создания структур переменного размера. Другое дело, я не уверен, как всё это смотрится с теоретической точки зрения.... :)

Sign up to leave a comment.