【问题标题】:Empty subobject optimization空子对象优化
【发布时间】:2020-04-15 18:17:52
【问题描述】:

我看到了 1997 年的文章,The "Empty Member" C++ Optimization,作者是 Nathan C. Meyers,其中讨论了将 STL 分配器等空对象“存储”为成员结构的基类的方法。他在最后顺便提到:

再次更新:根据编译器必须遵守的 ABI 规范,可以进行一系列相关的“空子对象”优化。 (几年前,Jason Merrill 向我指出了其中的一些。)例如,考虑(空)类型 A、B 和 C 的三个结构成员,以及第四个非空的结构成员。它们可以一致地占据相同的地址,只要它们彼此之间或与包含类没有任何共同的基数。实践中的一个常见问题是让一个类的第一个(或唯一一个)成员派生自与该类相同的空基。编译器必须插入填充,以便它们两个子对象具有不同的地址。这实际上发生在具有 interator 成员的迭代器适配器中,两者都派生自 std::iterator。一个不小心实现的标准 std::reverse_iterator 可能会出现这个问题。

有人可以用一个具体的例子来描述这个吗?我遗漏了一些东西。 Here's what I tried in Compiler Explorer:

#include <stdint.h>

typedef struct
{
    struct {} nothing;
    int16_t x;
    int16_t y;
} STRUCT_C;

typedef struct
{
    struct {} nothing_a;
    struct {} nothing_b;
    int16_t x;
    int16_t y;
} STRUCT_D;

typedef struct {} NOTHING_A;
typedef struct {} NOTHING_B;

typedef struct
{
    struct M : public NOTHING_A, NOTHING_B {
        int16_t x;
    } m;
    int16_t y;
} STRUCT_E;

int doit3(STRUCT_C *p)
{
    void *addr1 = p;
    void *addr2 = &p->x;
    return (int)((uint8_t *)addr2 - (uint8_t *)addr1);
}

int doit4(STRUCT_D *p)
{
    void *addr1 = p;
    void *addr2 = &p->x;
    return (int)((uint8_t *)addr2 - (uint8_t *)addr1);
}

int doit4a(STRUCT_D *p)
{
    void *addr1 = p;
    void *addr2 = &p->nothing_a;
    return (int)((uint8_t *)addr2 - (uint8_t *)addr1);
}

int doit4b(STRUCT_D *p)
{
    void *addr1 = p;
    void *addr2 = &p->nothing_b;
    return (int)((uint8_t *)addr2 - (uint8_t *)addr1);
}

int doit5a(STRUCT_E *p)
{
    void *addr1 = p;
    void *addr2 = &p->m.x;
    return (int)((uint8_t *)addr2 - (uint8_t *)addr1);
}

int doit5b(STRUCT_E *p)
{
    void *addr1 = p;
    void *addr2 = &p->y;
    return (int)((uint8_t *)addr2 - (uint8_t *)addr1);
}

从 clang 10.0.0 和 gcc 9.3 生成(xor eax, eax 在 x86-64 上生成 0):

doit3(STRUCT_C*):
        mov     eax, 2
        ret
doit4(STRUCT_D*):
        mov     eax, 2
        ret
doit4a(STRUCT_D*):
        xor     eax, eax
        ret
doit4b(STRUCT_D*):
        mov     eax, 1
        ret
doit5a(STRUCT_E*):
        xor     eax, eax
        ret
doit5b(STRUCT_E*):
        mov     eax, 2
        ret

这反映了我对 C++ 标准的理解,即空基类优化允许编译器不为空基类分配空间,但所有成员对象至少需要 1 个字节的空间以确保它们具有唯一的地址。 (并且编译器有对齐约束,如STRUCT_C,其中nothing 成员只需要1 个字节,但x 的偏移量是2 个字节)

Nathan Meyers 在他的“更新”中试图提出什么建议?

【问题讨论】:

    标签: c++


    【解决方案1】:

    据我了解,在您的情况下,这意味着所有nothing_anothing_bx 都可以具有相同的地址,因为nothing_anothing_b 都是空的并且没有共同的基类,所以没有两个相同类型的单独对象具有相同的地址。

    【讨论】:

    • 那是我认为我读到的内容,但这与我对 C++ 标准的理解并不相符,而且无论是 clang 还是 gcc 都不能用来演示。
    • C++ 标准没有明确允许这种优化 AFAIK,所以没有编译器应该这样做(MSVC 可以)。但正如我所看到的,关键是虽然指向不同对象的指针必须不同,但当对象具有不同类型时,无论实际值如何,指针的定义(也称为别名规则)都是不同的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-08-23
    • 2021-10-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-23
    相关资源
    最近更新 更多