怎么能不对 C++ 结构布局做出假设呢?
How can one not make assumptions about C++ struct layouts?
我刚刚从 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 或图形 APIs,我很好奇人们究竟可以假设什么,什么时候可以只使用结构作为内存块。我了解结构打包和对齐,但还有更多吗?
谢谢
的确,粗略地说,C++ 标准不强制要求 class 成员的任何特定内部布局。
但是,专业库(如用于特定操作系统的图形库)将以操作系统的特定编译器为目标。他们知道这个特定的编译器如何安排 C/C++ class 和结构成员的布局,并且该库将提供与所讨论的实际硬件相匹配的合适定义。
具有多个编译器的操作系统通常会对该操作系统的二进制 ABI 有一个正式的规范,编译器将遵循该 ABI,专业库将提供 class 和结构定义那将与那个同步。
因此,在您的特定情况下,您可以 "assume and when is it ok to just use a struct as a chunk of memory" 在查阅编译器的文档后,确定编译器如何布置结构或 classes 的成员,然后提出一个相应地结构布局。
重要的是要认识到你在你引用的问题中所做的和你在这里所做的之间的区别。
你在题目中所做的违反了C++的规则。它调用未定义的行为。您试图假装包含 16 个浮点数的对象与包含 16 个浮点数的数组相同。 C++ 不允许这是明确定义的行为,允许编译器假定您不会尝试它。
相比之下,将结构转换为字节数组并将该数组复制到其他地方实际上不会违反 C++ 对象模型的规则。它有一个非常具体的条款,允许对适当的类型进行此类操作。
区别在于 C++ 编译器 不关心对象的布局;这是GPU。只要您提供的数据布局与您的着色器所说的相匹配,就可以了。您不是将浮点数转换为数组,也不是试图通过指向另一个对象或类似对象的指针来访问一个对象。你只是在复制字节。
此时,剩下的唯一问题是该结构的字节表示是否与预期的 SPIR-V 数据结构定义的字节表示相匹配。是的,.
Spir-V(传递给 vulkan 的着色语言)要求您向用于 UniformConstant、Uniform 和 PushConstant 变量的结构成员添加布局装饰。您可以使用它来使 spir-V 成员偏移量与 C++ 结构中的成员偏移量相匹配。
要真正做到这一点很棘手,因为它需要检查 spir-V 代码并根据需要设置偏移量。
我刚刚从 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 或图形 APIs,我很好奇人们究竟可以假设什么,什么时候可以只使用结构作为内存块。我了解结构打包和对齐,但还有更多吗?
谢谢
的确,粗略地说,C++ 标准不强制要求 class 成员的任何特定内部布局。
但是,专业库(如用于特定操作系统的图形库)将以操作系统的特定编译器为目标。他们知道这个特定的编译器如何安排 C/C++ class 和结构成员的布局,并且该库将提供与所讨论的实际硬件相匹配的合适定义。
具有多个编译器的操作系统通常会对该操作系统的二进制 ABI 有一个正式的规范,编译器将遵循该 ABI,专业库将提供 class 和结构定义那将与那个同步。
因此,在您的特定情况下,您可以 "assume and when is it ok to just use a struct as a chunk of memory" 在查阅编译器的文档后,确定编译器如何布置结构或 classes 的成员,然后提出一个相应地结构布局。
重要的是要认识到你在你引用的问题中所做的和你在这里所做的之间的区别。
你在题目中所做的违反了C++的规则。它调用未定义的行为。您试图假装包含 16 个浮点数的对象与包含 16 个浮点数的数组相同。 C++ 不允许这是明确定义的行为,允许编译器假定您不会尝试它。
相比之下,将结构转换为字节数组并将该数组复制到其他地方实际上不会违反 C++ 对象模型的规则。它有一个非常具体的条款,允许对适当的类型进行此类操作。
区别在于 C++ 编译器 不关心对象的布局;这是GPU。只要您提供的数据布局与您的着色器所说的相匹配,就可以了。您不是将浮点数转换为数组,也不是试图通过指向另一个对象或类似对象的指针来访问一个对象。你只是在复制字节。
此时,剩下的唯一问题是该结构的字节表示是否与预期的 SPIR-V 数据结构定义的字节表示相匹配。是的,
Spir-V(传递给 vulkan 的着色语言)要求您向用于 UniformConstant、Uniform 和 PushConstant 变量的结构成员添加布局装饰。您可以使用它来使 spir-V 成员偏移量与 C++ 结构中的成员偏移量相匹配。
要真正做到这一点很棘手,因为它需要检查 spir-V 代码并根据需要设置偏移量。