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

Я только что узнал от Ошибка в компиляторе VC ++ 14.0 (2015)? что не следует делать предположений о том, как макет структуры окажется в памяти. Однако я не понимаю, насколько это обычная практика во многих кодах, которые я видел. Например, графический API 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;

Тогда на стороне графического процессора ubo всегда будет соответствовать uboVS.

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

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

Спасибо


person Hasan Al-Jawahiri    schedule 30.08.2016    source источник
comment
Никогда не следует использовать структуру в доменах компиляции. Иногда это может работать какое-то время, но, делая это по привычке, вам может потребоваться много обслуживания этого кода, когда другие решения могли быть написаны один раз и не требовали регулярного обслуживания. Если вы планируете сделать это для обеспечения безопасности работы, убедитесь, что вы можете попробовать это и посмотреть, насколько хорошо это сработает для вас.   -  person old_timer    schedule 30.08.2016
comment
Вы можете посмотреть этот вопрос и ответ.   -  person Nicol Bolas    schedule 30.08.2016
comment
Чтобы избежать предположений о макетах структур, не используйте memcpy в структурах (среди прочего)   -  person M.M    schedule 30.08.2016


Ответы (3)


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

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

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

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

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

person Nicol Bolas    schedule 30.08.2016

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

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

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

Итак, в вашем конкретном случае вы можете «предположить, и когда можно просто использовать структуру в качестве фрагмента памяти» после того, как вы проконсультируетесь с документацией вашего компилятора, определите, как ваш компилятор размещает элементы структур или классов, а затем с соответствующим макетом структуры.

person Sam Varshavchik    schedule 30.08.2016

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

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

person ratchet freak    schedule 30.08.2016