Как можно не делать предположений о структурных макетах C ++?

Я только что узнал от Ошибка в компиляторе VC ++ 14.0 (2015)? что не следует делать предположения о том, как макет структуры окажется в памяти. Тем не менее, я не понимаю, как это часто встречается в коде, который я видел. Например, графический интерфейс Vulkan выполняет следующие действия:

Определяет структуру

struct {
glm::mat4 projection;
glm::mat4 model;
glm::vec4 lightPos;
} uboVS;

Затем заполняет его поля:

    uboVS.model = ...
uboVS....

Затем просто копирует структуру (в памяти хоста) в память устройства через memcpy:

    uint8_t *pData;
vkMapMemory(device, memory, 0, sizeof(uboVS), 0, (void **)&pData);
memcpy(pData, &uboVS, sizeof(uboVS));
vkUnmapMemory(device, memory);

Затем к графическому процессору он определяет UBO для соответствия этой структуре:

layout (binding = 0) uniform UBO
{
mat4 projection;
mat4 model;
vec4 lightPos;
} ubo;

Тогда, на стороне GPU, UBO всегда будет соответствовать UBOVS.

Это то же самое неопределенное поведение? Разве этот код не полагается на структуру uboVS, которая должна быть выложена точно так, как определено, или для обеих сторон (скомпилированный код C ++ и скомпилированный шейдер SPIR-V) для генерации в основном одного и того же разметки структуры? (аналогично первому примеру в https://www.securecoding.cert.org/confluence/display/c/EXP11-C.+Do+not+make+assumptions+regarding+the+layout+of+structures+with+bit-fields)

Этот вопрос не является специфическим для Vulkan или графических API, мне любопытно, что именно можно предположить, и когда можно просто использовать структуру как кусок памяти. Я понимаю структуру упаковки и выравнивания, но есть ли что-то еще?

Спасибо

5

Решение

Важно понимать разницу между тем, что вы сделали в вопросе, который вы цитируете, и тем, что вы делаете здесь.

То, что вы сделали в показанном вопросе, нарушает правила C ++. Это вызывает неопределенное поведение. Вы пытались сделать вид, что объект, содержащий 16 чисел с плавающей запятой, — это то же самое, что и массив с 16 поплавками. C ++ не позволяет этому быть четко определенным поведением, и компиляторы могут предполагать, что вы не попробуете это.

Напротив, преобразование структуры в байтовый массив и копирование этого массива где-то еще на самом деле не нарушать правила объектной модели C ++. У этого есть очень определенное положение, разрешающее такие вещи для соответствующих типов.

Разница в том, что это не Компилятор C ++ это заботится о расположении объекта; это графический процессор Пока вы предоставляете макет данных, совпадающий с тем, что сказал ваш шейдер, все в порядке. Вы не приводите поплавки в массивы или пытаетесь получить доступ к одному объекту через указатель на другой объект или что-то подобное. Вы просто копируете байты.

В этот момент единственный вопрос, который остается, заключается в том, соответствует ли байтовое представление этой структуры байтовому представлению ожидаемого определения структуры данных SPIR-V. И да, это то, на что вы можете положиться в большинстве систем, на которых может работать Vulkan.

7

Другие решения

Это правда, что, грубо говоря, стандарт C ++ не требует какой-либо конкретной внутренней компоновки членов класса.

Однако специализированные библиотеки, такие как графические библиотеки для конкретной операционной системы, будут ориентированы на конкретный компилятор операционной системы. Они знают, как этот конкретный компилятор организует компоновку класса C / C ++ и членов структуры, и библиотека предоставит подходящие определения, соответствующие конкретному аппаратному обеспечению.

Операционные системы, имеющие более одного компилятора, часто будут иметь формальную спецификацию для двоичного ABI этой операционной системы, и компиляторы будут следовать этому ABI, а специализированная библиотека предоставит определения классов и структур, которые будут синхронизированы с этим.

Таким образом, в вашем конкретном случае вы можете «предположить, и когда это нормально, просто использовать структуру как кусок памяти» после того, как вы обратитесь к документации вашего компилятора, определите, как ваш компилятор распределяет элементы структур или классов, а затем приходит с макетом структуры соответственно.

5

Spir-V (язык шейдинга, который вы передаете vulkan) требует добавления декораций макета к элементам структуры, используемым для переменных UniformConstant, Uniform и PushConstant. Вы можете использовать это, чтобы смещения члена spir-V соответствовали смещениям члена в структуре C ++.

На самом деле это сложно, так как требует проверки кода spir-V и установки смещений по мере необходимости.

0
По вопросам рекламы [email protected]