C++ Можно ли оптимизировать данные константного класса вне класса компилятором?
Я знаю, что постоянные переменные вне классов могут быть оптимизированы напрямую в вызовы функций компилятором, но допустимо ли для компилятора делать то же самое для переменных постоянных классов?
Если есть класс, объявленный так:
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 ответа
Компилятор может свободно генерировать любой код, который сохраняет "наблюдаемое поведение" программы (есть исключение с конструктором копирования, который может быть исключен, даже если он имеет наблюдаемое поведение, но здесь он не применяется). Это называется как будто правило
struct X { int x; };
auto foo()
{
X x{24};
return x.x;
}
любой достойный компилятор оптимизирует вышесказанное так:
foo(): # @foo()
mov eax, 24
ret
Как видите, это не имеет ничего общего с константностью (ну, почти), просто с наблюдаемым поведением. Вы можете попробовать поиграть в сложность кода и посмотреть, насколько умным является компилятор, чтобы выяснить, может ли он удалить код, связанный с экземпляром класса. Подсказка: это очень умно.
Мне не понятно, что вы имеете в виду под этим:
разрешено ли компилятору делать это и уменьшать размер класса (int)?:
Я могу вам сказать, что: для типа X
и объект x
такого типа sizeof(x)
всегда = sizeof(X)
независимо от экземпляров класса. Другими словами, размер класса определяется, когда класс определен, и, как таковой, на него не влияют возможные реализации или его отсутствие. Размер класса является суммой всех размеров его нестатических элементов данных плюс заполнение. Заполнение определяется реализацией и может обычно управляться (например, упакованными структурами). Так что нет, размер класса никогда не может быть меньше суммы размеров всех его нестатических не ссылочных элементов данных.
Для компилятора было бы совершенно законно сгенерировать
int absoluteVal = 2;
Если abs
не имеет побочных эффектов. Все это зависит от "наблюдаемого поведения" ( как-будто правило). Если вы не можете сказать извне, что компилятор выполнил какое-то преобразование, то это допустимо для компилятора.
Оптимизация кода и структура памяти объекта не подчиняются тем же правилам
Стандарт C++ гласит следующее о расположении объектов в памяти:
1.8 / 2: объекты могут содержать другие объекты, называемые подобъектами. Субобъект может быть подобъектом-членом, подобъектом базового класса или элементом массива. (...)
9.2 / 13: Нестатические члены данных (не объединяющего) класса с одинаковым контролем доступа распределяются так, чтобы более поздние члены имели более высокие адреса в объекте класса. Порядок распределения нестатических элементов данных с различным контролем доступа не определен. Требования выравнивания реализации могут привести к тому, что два смежных элемента не будут выделяться сразу после друг друга; то же самое касается требований к пространству для управления виртуальными функциями и виртуальными базовыми классами.
Это гарантирует, что нестатические члены const (которые являются членами данных, даже если они являются const) содержатся внутри объекта. Таким образом, компилятору не разрешается сокращать размер объекта.
Однако компилятору разрешается выполнять оптимизацию кода, такую как постоянное распространение и устранение мертвого кода, переупорядочение и т. Д., Если наблюдаемое поведение не изменяется:
1.9 / 5: Соответствующая реализация, выполняющая правильно сформированную программу, должна производить то же наблюдаемое поведение, что и одно из возможных исполнений соответствующего экземпляра абстрактной машины с той же программой и тем же вводом. (...)
Так что если ваш постоянный член не 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)
Это потому, что наблюдаемое поведение записи этого тривиально копируемого объекта в поток требует, чтобы каждый байт объекта соответствовал ожиданиям.