【问题标题】:Is the compiler allowed to optimise out private data members?编译器是否允许优化私有数据成员?
【发布时间】:2021-03-19 22:13:06
【问题描述】:

如果编译器可以证明一个类的(私有)成员从未被使用过,包括潜在的朋友,标准是否允许编译器从类的内存占用中删除该成员?

不言而喻,在编译时这对于受保护成员或公共成员是不可能的,但在某些情况下,可能会针对私有数据成员构建这样的证明。


相关问题:

【问题讨论】:

  • *具有相同访问控制(第 11 条)的(非联合)类的非静态数据成员被分配,以便后面的成员在类对象中具有更高的地址。 * (9.2.14)。但是,我不清楚这是否意味着编译器无法删除元素。被移除的元素不会有更高的地址...,但它不需要地址...
  • 只是想在这里寻找扳手投入工作......但是如果其他地方的代码将使用sizeof(myClass) 类型的操作怎么办?我真的想不出为什么会这样做,但如果一个成员被优化掉,那可能会破坏。
  • @AdrianMole:非常好的一点——即使潜在的访问是有限的(朋友和成员),即使所有这些有限的访问在一个编译单元中对编译器都是可见的,它也需要假设sizeof(T) 用于其他一些对某些定义缺乏可见性的编译单元。由于其他编译单元无法执行优化,因此没有编译单元可以执行优化。
  • @AdrianMole 为什么会中断?类型的大小取决于编译器 IIRC。无论如何都允许将大小膨胀到超出成员所需的原始字节,那么为什么不减少它呢?
  • @AdrianMole “我真的想不出它为什么会这样做”——它对每个数组访问都这样做(在后台)。但正如 OP 正确指出的那样,大小不一定反映类的确切成员。

标签: c++ optimization compilation language-lawyer


【解决方案1】:

如果编译器可以证明一个类的(私有)成员从未被使用过

编译器不能证明这一点,因为私有成员可以在其他编译单元中使用。具体来说,this is possible in the context of a pointer to member in a template argument按照[temp.spec]/6的标准,为originally described by Johannes Schaub

因此,总而言之:不,编译器不得优化私有数据成员,而不是优化公共或受保护成员(遵守 as-if 规则)。

【讨论】:

  • 如果每个成员和朋友在当前编译单元中都有一个定义怎么办?其他 CU 中的定义不能不同,这将违反 ODR。关键似乎是 any 代码可以通过在模板参数中这样做来命名私有成员。
  • @BenVoigt 我不明白你的意思。我的回答与 ODR 无关。
  • 帮助我理解这个论点。在我看来,这是一个编译器无法证明该成员无法在外部访问的示例。但这如何表明可能存在 no 示例?所有函数都定义在同一个 CU 中的非模板类呢?甚至忘记成员函数,这个类呢:class A { int x; };.
  • @bitmask 我不是在谈论类模板。我说的是具有私有成员的常规类,这些私有成员在模板参数列表中使用(作为指向成员的指针)。您的类 A 仍然可以在模板参数列表内的另一个翻译单元中使用,以访问 &A::x
【解决方案2】:

不行,因为你可以颠覆门禁系统legally

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

【讨论】:

  • ISO C++ 没有指定必须有 ABI。我添加了一个答案,指出不允许库的全程序优化编译器在理论上可以做到这一点,因为它知道它可以看到一切。不过,您的回答为允许单独编译库的编译器敲响了警钟。即我们实际想要使用的实现类型。
  • 在我的实验(C++17)中,您甚至不需要tag 类型或n 参数,从而消除了警告。我不明白(也许你可以在你的回答中详细说明)为什么cheat&lt;&amp;A::x, 0&gt; 甚至是合法的。
  • @bitmask 我的回答解释了为什么它是合法的(tl;dr:这是由于 [temp.spec]/6)。
  • @KonradRudolph 是的,你的回答和这个一起形成了一个很好的解释。但是,我无法单独理解它们。
【解决方案3】:

理论上可行(以及未使用的公共成员),但不是我们习惯的那种编译器生态系统(针对可以链接单独编译的代码的固定 ABI)。 只能通过禁止单独库的整个程序优化来删除未使用的成员1

其他编译单元可能需要就 sizeof(foo) 达成一致,但如果它依赖于验证成员函数的行为的实现不依赖于任何私有成员,那将不是你可以从 .h 派生的东西。

请记住,C++ 只真正指定了一个程序,而不是一种创建库的方式。 ISO C++ 指定的语言与我们习惯的实现风格兼容(当然),但同时获取所有.cpp.h 文件并生成单个独立的不可扩展可执行文件的实现是可能。

如果您对实现进行了足够的限制(没有固定的 ABI),则可以在整个程序中积极应用 as-if 规则。


脚注 1:如果编译器已经可以看到声明的每个成员函数的定义,我将添加“或以某种方式将大小信息导出到正在编译的其他代码”作为允许库的一种方式在课堂里。但是@PasserBy 的回答指出,单独编译的库可能是以最终产生外部可见副作用(如 I/O)的方式使用声明的私有成员的东西。所以我们必须完全排除它们。

鉴于此,就此类优化而言,公共成员和私有成员是等效的。

【讨论】:

  • “删除未使用的私有成员可以……通过整个程序优化来完成……”当然可以,但公共成员也是如此,因为在 as-if 规则下这是一个微不足道的转换。
  • @KonradRudolph:确实如此。非常真实。这是“语言律师”的答案,而不是“给定的我们绝对不想改变的现实世界实施技术”的答案。
  • “整个程序优化”是否会计算 LTO?
  • @lights0123:是的,这将是实现它的一种方式。但这确实避免了不能链接新代码的约束,因此它与当前的编译器 LTO 非常不同,其中 .o.so/.dll 没有 LTO 部分可以作为不透明的不可内联定义链接。即 每个 对象必须是 LTO,没有已经单独优化为机器代码的库。关键是你可以告诉编译器这是所有的源文件,比如gcc -fwhole-program。不仅仅是“您可以优化其中一些目标文件”。
  • @Joshua:像writeread 这样的I/O 函数带有void*char* 到对象表示将是“使用”,因为它将是外部的- 程序存储在那里的任何内容的可观察视图。
猜你喜欢
  • 2018-12-30
  • 1970-01-01
  • 2019-04-21
  • 2019-04-29
  • 2015-02-28
  • 1970-01-01
  • 1970-01-01
  • 2013-10-23
相关资源
最近更新 更多