编译器是否允许优化掉私有数据成员?

32
如果编译器能够证明类的(私有)成员变量从未被使用,包括潜在的友元函数,那么标准是否允许编译器从类的内存占用中删除该成员变量?
显然,对于公有或保护成员变量,在编译时无法实现这一点,但是在某些情况下,可以构造出这样的证明来处理私有数据成员。

相关问题:


1
一个(非联合)类的非静态数据成员,如果有相同访问控制权限(第11条款),那么它们在分配时会使后面的成员在类对象中拥有更高的地址。(9.2.14)。然而,我不确定这是否意味着编译器无法删除元素。被删除的元素将不会有更高的地址,但它也不需要地址... - Damien
1
只是试图寻找一个扰乱局面的方法...但如果其他地方的代码使用sizeof(myClass)这样的操作呢?我真的想不出为什么会这样,但如果成员被优化掉了,那可能会导致错误。 - Adrian Mole
@AdrianMole:非常好的一点——即使潜在访问是有限的(朋友和成员),即使所有这些有限的访问在一个编译单元中对编译器可见,它仍需要假设sizeof(T)在某个其他编译单元中使用,该编译单元缺乏对某些定义的可见性。由于另一个编译单元将无法执行优化,因此没有编译单元可以执行优化。 - Ben Voigt
2
@AdrianMole 为什么会出问题?据我所知,类型的大小取决于编译器。它可以将大小膨胀到超出成员所需的原始字节,因此为什么不能将其缩小呢? - bitmask
问这个问题肯定不会伤害你。我有点嫉妒,但还是遵守了承诺。;-) - Yunnosch
显示剩余3条评论
3个回答

32
理论上可能实现(包括未使用的公共成员),但是不符合我们通常使用的编译器生态系统的要求(目标是一个可以链接单独编译代码的固定ABI)。仅通过整个程序优化才能删除未使用的成员,这将禁止单独的库1
其他编译单元可能需要对`sizeof(foo)`达成一致,但如果它取决于验证任何私有成员的成员函数行为的实现是否依赖于它,则不能从`.h`派生出来。
请记住,C++ 实际上只指定了一个程序,而不是库的实现方式。ISO C++ 指定的语言兼容我们通常使用的实现风格(当然),但是将所有 `.cpp` 和 `.h` 文件一起处理并生成单个自包含的不可扩展可执行文件的实现也是可能的。
如果足够约束实现(没有固定的ABI),则可以积极地整个程序应用as-if规则。

注1:我本来想加上“或者将大小信息导出给正在编译的其他代码”这种方式,以允许库(如果编译器已经能够看到类中声明的每个成员函数的定义)进行优化。但是@PasserBy的回答指出,一个单独编译的库可以使用声明的私有成员以一些最终会产生外部可见副作用的方式(比如输入输出)。因此我们必须完全排除它们。

鉴于此,对于这种优化,公共成员和私有成员是等价的。


1
“删除未使用的私有成员可以通过整个程序优化来完成……”当然,对于公共成员也是如此,因为在 as-if 规则下这是一个微不足道的转换。 - Konrad Rudolph
@KonradRudolph:确实。非常正确。这是“语言律师”的答案,而不是“考虑到实际实现技术,我们绝对不想改变”的答案。 - Peter Cordes
整个程序优化,LTO 算吗? - lights0123
1
@lights0123:是的,那是一种实现的方法。但这并不能避免不能链接新代码的限制,因此它与当前编译器的LTO非常不同,后者可以将没有LTO部分的".o"或".so"/".dll"作为不透明的非内联定义链接。也就是说,每个对象都必须是LTO,没有已经单独优化为机器代码的库。关键点在于您可以告诉编译器这就是全部源文件,就像gcc -fwhole-program一样。不仅仅是“您可以跨某些目标文件进行优化”。 - Peter Cordes
2
@Joshua:像writeread这样的I/O函数,使用void*char*到对象表示的方式将是一个“用途”,因为它将是程序存储在那里的任何东西的外部可观察视图。 - Peter Cordes
显示剩余3条评论

18

1
如果每个成员和朋友在当前编译单元中都有定义会怎样?这些定义不能在其他编译单元中不同,否则将违反ODR。关键似乎是任何代码都可以通过在模板参数中命名私有成员来实现。 - Ben Voigt
@BenVoigt 我不明白你的意思。我的回答与ODR无关。 - Konrad Rudolph
帮我理解这个参数。在我看来,这是一个编译器无法证明成员不能在外部访问的示例。但是这如何表明根本就没有任何示例呢?那么非模板类呢?其中所有函数都在同一CU中定义?甚至忘记成员函数,那么这个类呢:class A { int x; }; - bitmask
@bitmask 我不是在谈论类模板。我说的是带有私有成员的常规类,这些成员在模板参数列表中被使用(作为成员指针)。你的类A仍然可以在另一个翻译单元中用于模板参数列表中访问&A::x - Konrad Rudolph

13

不行,因为你可以通过合法的方式破坏访问控制系统。

class A
{
    int x;
};

auto f();

template<auto x>
struct cheat
{
    friend auto f() { return x; }
};

template struct cheat<&A::x>;  // see [temp.spec]/6

int& foo(A& a)
{
    return a.*f();  // returns a.x
}

考虑到编译器必须在第一次使用A时修复ABI,并且它永远不知道将来的某些代码是否可以访问x,因此它必须固定A的内存以包含x


5
ISO C++ 标准没有规定必须存在 ABI。我添加了一篇回答,指出一个整个程序优化编译器在理论上可以做到这一点,因为它知道它正在看到所有内容,并且不允许使用库。但是,你的回答就是给支持分别编译库的编译器戴上了绝杀帽子。也就是说,我们实际想要使用的那种实现方式。 - Peter Cordes
3
在我的实验中(使用C++17),似乎不需要tag类型或n参数也可以消除警告。我不明白的是,为什么cheat<&A::x, 0>甚至是合法的,希望你在回答中能够详细说明。 - bitmask
@bitmask 我的回答解释了为什么它是合法的(简而言之:这是由于[temp.spec]/6)。 - Konrad Rudolph
@KonradRudolph 是的,你的回答和这个回答一起构成了一个很好的解释。但是,我无法单独理解它们两个。 - bitmask

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接