【发布时间】: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++