我刚刚从Bug in VC++ 14.0 (2015) compiler?得知,不应该假设结构的布局最终会在内存中出现。但是,我不明白我见过很多代码中的常见做法。例如,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+bit-fields中的第一个例子)
这个问题并不是特定于Vulkan或图形API,我很好奇人们可以假设什么,以及什么时候只使用结构作为一块内存。我理解结构打包和对齐,但还有更多吗?
由于
答案 0 :(得分:7)
重要的是要认识到你在引用的问题中做了什么和你在这里做了什么之间的区别。
你在所展示的问题中做了什么违反了C ++的规则。它调用未定义的行为。您试图假装包含16个浮点数的对象与16个浮点数组相同。 C ++不允许这是明确定义的行为,并允许编译器假设您不会尝试它。
相比之下,将结构转换为字节数组并将该数组复制到其他地方实际上不会破坏C ++对象模型的规则。它有一个非常具体的条款允许适当类型的事情。
不同之处在于 C ++编译器不关心对象的布局;这是GPU。只要您提供的数据布局与着色器所说的相匹配,您就可以了。您没有将浮动数据转换为数组或尝试通过指向另一个对象或某些对象来访问一个对象。你只是在复制字节。
此时,唯一的问题是该结构的字节表示是否与预期的SPIR-V数据结构定义的字节表示相匹配。是的,this is something you can rely upon for most systems that Vulkan can run on。
答案 1 :(得分:5)
粗略地说,C ++标准并没有强制要求任何特定的类成员内部布局。
然而,专业库,如特定操作系统的图形库,将成为操作系统特定编译器的目标。他们知道这个特定的编译器如何安排C / C ++类和结构成员的布局,并且库将提供与所讨论的实际硬件相匹配的合适定义。
具有多个编译器的操作系统通常具有该操作系统的二进制ABI的正式规范,编译器将遵循该ABI,并且专业库将提供将与之同步的类和结构定义。这一点。
因此,在您的具体情况下,在查阅编译器的文档后,您可以“假设并且何时可以使用结构作为一块内存”,确定编译器如何布局结构或类的成员,然后相应地提出结构布局。
答案 2 :(得分:0)
Spir-V(传递给vulkan的着色语言)要求您为用于UniformConstant,Uniform和PushConstant变量的结构成员添加布局装饰。您可以使用它来使spir-V成员偏移量与C ++结构中的成员偏移量匹配。
实际上要做到这一点很棘手,因为它需要检查spir-V代码并根据需要设置偏移量。