Может ли компилятор оптимизировать данные константного класса вне класса?


Я знаю, что постоянные переменные вне классов могут быть оптимизированы компилятором непосредственно в вызовы функций, но законно ли компилятору делать то же самое для постоянных переменных класса?

Если существует класс, объявленный следующим образом:

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);
3 3

3 ответа:

Компилятор может свободно выдавать любой код, который сохраняет "наблюдаемое поведение" программы (есть исключение с конструктором копирования, которое может быть устранено, даже если оно имеет наблюдаемое поведение, но оно не применяется здесь). Это называется как если бы Правило

struct X { int x; };

auto foo()
{
  X x{24};

  return x.x;
}

Любой приличный компилятор оптимизирует вышесказанное до этого:

foo():                                # @foo()
        mov     eax, 24
        ret
Как вы можете видеть, это не имеет ничего общего с постоянством (ну, почти), просто с наблюдаемым поведением. Вы можете попробовать и поиграть с добавление кода в сложность и посмотреть, насколько умный компилятор в выяснении он может удалить код, связанный с экземпляром класса. Подсказка: это очень умно.
Мне не совсем ясно, что вы имеете в виду под этим:

Можно ли компилятору сделать это вместо этого и сделать класс sizeof (int) меньше?:

Я могу сказать вам, что: для типа X и Объекта x такого типа sizeof(x) Всегда = sizeof(X) независимо от экземпляров класса. Другими словами, размер класса определяется, когда класс определен,и как таковой он не зависит от возможных экземпляров или отсутствия. Размер класса-это сумма всех размеров его нестатических элементов данных плюс заполнение. Заполнение определяется реализацией и обычно может быть несколько контролируемым (например, упакованные структуры). Так что нет, размер класса никогда не может быть меньше суммы размеров всех его нестатических несвязанных элементов данных.

Было бы совершенно законно для компилятора генерировать

int absoluteVal = 2;

Если abs не имеет побочных эффектов. Все это зависит от" наблюдаемого поведения " (правило as-if). Если вы не можете сказать извне, что компилятор произвел некоторое преобразование, то это законно для компилятора, чтобы сделать это преобразование.

Оптимизация кода и компоновка памяти объектов не подчиняются одним и тем же правилам

Стандарт C++ утверждает следующее о расположении памяти объектов :

1.8/2: объекты могут содержать другие объекты, называемые подобъектами. Подобъект может быть подобъектом-членом, подобъектом базового класса или элемент массива. (...)

9.2/13: нестатические члены данных класса (не объединенного) с одинаковым управлением доступом распределяются так что более поздние члены имеют более высокие адреса внутри объекта класса. Порядок выделения нестатических члены данных с различным управлением доступом не указаны. Требования к выравниванию реализации могут привести к появлению двух соседних элементов не должны быть распределены сразу после друг друга; так могли бы требования к пространству для управления виртуальными функциями и виртуальной базой занятия.

Это гарантирует, что нестатические члены const (которые являются членами данных, даже если они являются const) содержатся внутри объекта. Поэтому компилятору не разрешается уменьшать размер объекта.

Однако компилятор уполномочен выполнять оптимизацию кода , такую как постоянное распространение и устранение мертвого кода, переупорядочивание и т. д. до тех пор, пока наблюдаемое поведение не изменяется:

1.9/5: соответствующая реализация, выполняющая хорошо сформированную программу, должна производить то же наблюдаемое поведение, что и одна из возможных расстрелы населения соответствующий экземпляр абстрактной машины с та же программа и тот же ввод. (...)

Поэтому, если ваш член const не является ни volatile, Ни 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) 
Это происходит потому, что наблюдаемое поведение записи этого тривиально копируемого объекта в поток требует, чтобы каждый байт объекта соответствовал ожиданиям.