5

我刚刚从VC++ 14.0 (2015) 编译器中的错误中学到了?不应该假设结构的布局将如何在内存中结束。但是,我不明白在我见过的很多代码中这是多么普遍的做法。例如,Vulkan 图形 API 执行以下操作:

定义一个结构

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);

然后到 GPU,它定义了一个 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+位域

这个问题并不特定于 Vulkan 或图形 API,我很好奇人们究竟可以假设什么以及何时可以将结构用作一块内存。我了解结构打包和对齐,但还有更多吗?

谢谢

4

3 回答 3

7

重要的是要认识到您在引用的问题中所做的事情与您在此处所做的事情之间的区别。

您在所展示的问题中所做的事情违反了 C++ 的规则。它调用未定义的行为。您试图假装包含 16 个浮点数的对象与 16 个浮点数数组相同。C++ 不允许这是明确定义的行为,并且允许编译器假设您不会尝试它。

相比之下,将结构转换为字节数组并将该数组复制到其他地方实际上不会违反 C++ 对象模型的规则。它有一个非常具体的条款,允许对适当的类型进行此类操作。

不同之处在于关心对象布局的不是C++ 编译器。这是GPU。只要您提供的数据布局与您的着色器所说的相匹配,您就可以了。您不会将浮点数转换为数组或尝试通过指向不同对象或类似对象的指针访问一个对象。你只是在复制字节。

此时,剩下的唯一问题是该结构的字节表示是否与预期的 SPIR-V 数据结构定义的字节表示相匹配。是的,对于大多数 Vulkan 可以运行的系统,这是您可以依赖的

于 2016-08-30T03:15:44.803 回答
5

的确,粗略地说,C++ 标准并没有规定类成员的任何特定内部布局。

但是,专用库(例如特定操作系统的图形库)将针对操作系统的特定编译器。他们知道这个特定的编译器如何安排 C/C++ 类和结构成员的布局,并且该库将提供与所讨论的实际硬件相匹配的合适定义。

具有多个编译器的操作系统通常会对该操作系统的二进制 ABI 有一个正式的规范,编译器将遵循该 ABI,专业库将提供与之同步的类和结构定义。

因此,在您的特定情况下,您可以在查阅编译器的文档、确定编译器如何布置结构或类的成员之后“假设以及何时可以将结构用作一块内存”,然后来相应地进行结构布局。

于 2016-08-30T02:43:11.367 回答
0

Spir-V(传递给 vulkan 的着色语言)要求您向用于 UniformConstant、Uniform 和 PushConstant 变量的结构成员添加布局装饰。您可以使用它来使 spir-V 成员偏移量与 C++ 结构中的成员偏移量相匹配。

要真正做到这一点很棘手,因为它需要检查 spir-V 代码并根据需要设置偏移量。

于 2016-08-30T09:14:18.137 回答