首页 > 解决方案 > 使用 offsetof 是否保证正确?

问题描述

我读过 offsetof 宏通常实现为:

#define offsetof(st, m) \
    ((size_t)&(((st *)0)->m))

根据维基百科的说法,这是否是未定义的行为存在争议,因为它可能会取消引用指针。我有一个需要发送到不同地方的信息缓冲区,但缓冲区只占用结构的一部分。我想知道是否保证以下内容可以将我的结构的正确大小提供到缓冲区的末尾,即最后一个成员。

struct PerObjBuffer
{
    mat4 matrix;
    mat4 otherMatrix;
    vec4 colour;
    int sampleType = 0;

    size_t getBufferSize() { return offsetof(PerObjBuffer, sampleType) + sizeof(sampleType); }
    void sendToGPU() { memcpy(destination, this, getBufferSize()); }
    String shaderStringCode =

        R"(layout(binding = 2, std140) uniform perObjectBuffer
    {
        mat4 WVPMatrix;
        mat4 worldMatrix;
        vec4 perObjColour;
        int texSampleFormat;
        };
    )";

}

如果使用 offsetof 不是一个好主意,那么做我想做的最好的方法是什么?

编辑: sizeof(PerObjBuffer) - sizeof(String) 怎么样?我是否必须考虑填充问题?

标签: c++offsetof

解决方案


该标准指定事物的行为,而不是它们如何实现。实现部分留给实现者——编写编译器和库的人——他们必须以标准指定的方式实现事物。如果标准库的实现本身具有未定义的行为是无关紧要的 - 它必须与指定的编译器一起工作,因此特定的编译器将以它具有实现者想要的行为的方式解释它。未定义的行为意味着标准不指定对代码行为的要求。您的编译器文档可能会指定其他要求,指定根据标准未定义的代码行为 - 因此代码可能未由标准定义,并且在需要/编写以以这种方式解释它的特定编译器上是完全合理和合理的。

C++ 语言在使用适当#include的情况下使用来自 C 语言的宏。C标准说C99 7.17p3

 The macros are [...] and

        offsetof(type, member-designator)

which expands to an integer constant expression that has type size_t, the value of which is the offset in bytes, to the structure member (designated by member-designator), from the beginning of its structure (designated by type). The type and member designator shall be such that given

        static type t;

then the expression &(t.member-designator) evaluates to an address constant. (If the specified member is a bit-field, the behavior is undefined.)

作为该语言的用户,您并不关心它是如何实现的。如果您使用符合标准的编译器,无论它在幕后做什么,它都应该导致标准指定的行为。您的使用offsetof(PerObjBuffer, sampleType)是有效的 -static PerObjBuffer t;然后&(t.member-sampleType)计算为地址常量 - 因此offsetof(PerObjBuffer, sampleType)计算为整数常量表达式。您喜欢不关心编译器如何得出结果 - 它可以使用黑魔法来完成 - 编译器执行它并且结果表示以字节为单位的偏移量。(我认为著名的例子是memcpy- 不可能以memcpy符合标准的方式实现,但功能......存在)。

不过,我担心您的代码的其他部分会非常无效。这memcpy很可能会导致未定义的行为 - 您将在成员之间复制填充,并且您似乎希望将其发送到某个硬件位置。无论如何,我建议对 C++ 对象的生命周期和表示、结构内的填充、对象类型(即POD/standard layout/trivial)以及如何使用它们(即何时可以使用mem*函数)进行广泛的研究/放置新/ std::launder)。


推荐阅读