3

我知道类外的常量变量可以由编译器直接优化为函数调用,但是编译器对常量类变量做同样的事情是否合法?

如果有这样声明的类:

class A {
public:
const int constVar;
    //other, modifiable variables

A(int val): constVar(val) {
         //code to initialize modifiable variables

}
};

我创建了一个 A 的实例并调用这样的函数:

A obj(-2);
int absoluteVal = std::abs(A.constVar);

是否允许编译器这样做并使类sizeof(int)更小?:

A obj();
int absoluteVal = std::abs(-2);
4

3 回答 3

9

编译器可以自由地发出任何保留程序“可观察行为”的代码(复制构造函数有一个例外,即使它具有可观察行为也可以省略,但它不适用于此处)。这被称为好像规则

struct X { int x; };

auto foo()
{
  X x{24};

  return x.x;
}

任何体面的编译器都会对此进行优化:

foo():                                # @foo()
        mov     eax, 24
        ret

如您所见,它与 constness (嗯,几乎)没有任何关系,只是与可观察的行为有关。您可以尝试使用增加复杂性的代码,看看编译器在弄清楚它可以删除与类实例相关的代码方面有多聪明。提示:它非常聪明。


我不清楚你的意思是什么:

编译器是否允许这样做并使类 sizeof(int) 更小?:

我可以告诉你:对于一个类型Xx这种类型的对象sizeof(x)总是= sizeof(X)不管类的实例化。换句话说,类的大小是在定义类时确定的,因此它不受可能的实例化或缺少的影响。类的大小是其非静态数据成员的所有大小加上填充的总和。填充是实现定义的,通常可以在一定程度上进行控制(例如打包结构)。所以不,一个类的大小永远不能小于它的所有非静态非引用数据成员的大小之和。

于 2017-07-31T14:20:12.570 回答
2

编译器生成是完全合法的

int absoluteVal = 2;

如果abs没有副作用。这一切都取决于“可观察的行为”(as-if rule)。如果您无法从外部判断编译器进行了某些转换,那么编译器进行该转换是合法的。

于 2017-07-31T14:10:17.627 回答
2

代码优化和对象内存布局不遵循相同的规则

C++ 标准对对象的内存布局规定了以下内容:

1.8/2:对象可以包含其他对象,称为子对象。子对象可以是成员子对象、基类子对象或数组元素。(...)

9.2/13:分配具有相同访问控制的(非联合)类的非静态数据成员,以便以后的成员在类对象中具有更高的地址。未指定具有不同访问控制的非静态数据成员的分配顺序。实现对齐要求可能会导致两个相邻的成员不会被立即分配;管理虚拟功能和虚拟基类的空间需求也是如此。

这保证了对象中包含非静态 const 成员(它们是数据成员,即使它们是 const 也是如此)。所以编译器不允许缩短对象的大小。

但是,只要不改变可观察行为,编译器就有权执行代码优化,例如常量传播和死代码消除、重新排序等:

1.9/5:执行格式良好的程序的一致实现应产生与具有相同程序和相同输入的抽象机的相应实例的可能执行之一相同的可观察行为。(...)

因此,如果您的 const 成员不是volatilenor atomic<>,编译器可以很好地生成

A obj();              // size not touched.  And const member will be initialized if needed
int absoluteVal = 2;  // constant propagation + inlining (the object is not even accessed)

附加信息

这是一个无法优化对象的示例:

A obj(-2);                                 // object is constructed
int absoluteVal = std::abs(obj.constVar);  // will be optimized a way into = 2 
std::cout<<absoluteVal<<std::endl;
size_t lo = sizeof(obj);
std::cout<<lo<<std::endl; 
std::cout.write((char*)&obj, lo);         // obj is written to a stream 
                                          // and output of content at &obj adress is observable behavior

您可以在线查看优化器结果:尽管计算已absoluteVal被优化掉,但对象仍以全长实例化并初始化其常量

    ...
    mov     esi, 2                      ; this is absoluteVal calculation
    mov     DWORD PTR [rsp+12], -2      ; the const in [rsp+12] object is nevertheless initialized
    ...
    lea     rsi, [rsp+12]               ; the address of the object 
    mov     edx, 4                      ; and its length 
    ...                                 ; are used to call cout.write()
    call    std::basic_ostream<char, std::char_traits<char> >::write(char const*, long) 

这是因为将这个可简单复制的对象写入流的可观察行为要求对象的每个字节都符合预期。

于 2017-07-31T14:31:47.930 回答