В C ++, почему некоторые компиляторы отказываются помещать объекты, состоящие только из двойного, в регистр?

В разделе 20 Скотта Мейера Эффективный C ++, он утверждает:

некоторые компиляторы отказываются помещать объекты, состоящие только из двойника, в регистр

При передаче встроенных типов по значению компиляторы с радостью помещают данные в регистры и быстро отправляют ints/doubles/floats/так далее. вместе. Однако не все компиляторы будут обрабатывать небольшие объекты с одинаковой грацией. Я легко могу понять, почему компиляторы относятся к объектам по-разному — передать объект по значению может быть намного больше, чем копировать элементы данных между виртуальной таблицей и всеми конструкторами.

Но до сих пор. Это кажется легкой проблемой для современный Компиляторы, чтобы решить: «Этот класс маленький, может быть, я могу относиться к нему по-другому». Казалось, утверждение Майера подразумевает, что компиляторы МОГУТ осуществлять эту оптимизацию для объектов, состоящих только из int (или же char или же short).

Может кто-то дать дальнейшее понимание того, почему эта оптимизация иногда не происходит?

16

Решение

Я нашел этот документ онлайн на «Соглашения о вызовах для различных компиляторов C ++ и операционных систем«(обновлено в 2018-04-25), в котором имеется таблица с описанием» Методы передачи объектов структуры, класса и объединения «.

Из таблицы видно, что если объект содержит long doubleкопия всего объекта передается в стек для всех показанных здесь компиляторов.

введите описание изображения здесь

Также из того же ресурса (с добавлением акцента):

Существует несколько различных методов передачи параметра в функцию, если параметр является структурой, классом или объектом объединения. Копия объекта всегда создается, и эта копия передается вызываемой функции либо в регистрах, в стеке, либо с помощью указателя, как указано в таблице 6. Символы в таблице указывают, какой метод использовать. S имеет приоритет над I и R. PI и PS имеют приоритет над всеми другими методами передачи.

Как видно из таблицы 6, объект не может быть перенесен в регистры, если он слишком большой или слишком сложный. Например, объект, имеющий конструктор копирования, не может быть перенесен в регистры, поскольку конструктору копирования требуется адрес объекта. Конструктор копирования вызывается вызывающей стороной, а не вызываемой.

Объекты, передаваемые в стеке, выравниваются по размеру слова в стеке, даже если желательно более высокое выравнивание. Объекты, передаваемые указателями, не выровнены ни одним из изученных компиляторов, даже если выравнивание явно запрашивается. 64-битный Windows ABI требует, чтобы объекты, переданные указателями, были выровнены по 16.

Массив рассматривается не как объект, а как указатель, и копия массива не создается, за исключением случаев, когда массив заключен в структуру, класс или объединение.

64-разрядные компиляторы для Linux отличаются от ABI (версия 0.97) в следующих отношениях: Объекты с наследованием, функциями-членами или конструкторами могут передаваться в регистрах. Объекты с конструктором копирования, деструктором или виртуальным объектом передаются указателями, а не в стеке.

Компиляторы Intel для Windows совместимы с Microsoft. Компиляторы Intel для Linux совместимы с Gnu.

14

Другие решения

Вот пример, показывающий, что LLVM clang с уровнем оптимизации O3 обрабатывает класс с одним двойным членом данных так же, как это было с двойным:

$ cat main.cpp
#include <stdio.h>
class MyDouble {
public:
double d;
MyDouble(double _d):d(_d){}
};
void foo(MyDouble d)
{
printf("%lg\n",d.d);
}
int main(int argc, char **argv)
{
if (argc>5)
{
double x=(double)argc;
MyDouble d(x);
foo(d);
}
return 0;
}

Когда я компилирую его и просматриваю сгенерированный файл с битовым кодом, я вижу, что foo ведет себя
как будто он действует на double введите входной параметр:

$ clang++ -O3 -c -emit-llvm main.cpp
$ llvm-dis main.bc

Вот соответствующая часть:

; Function Attrs: nounwind uwtable
define void @_Z3foo8MyDouble(double %d.coerce) #0 {
entry:
%call = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([5 x i8]* @.str, i64 0, i64 0), double %d.coerce)
ret void
}

Смотри как foo объявляет свой входной параметр как doubleи перемещает его
печать « как есть «. Теперь давайте скомпилируем точно такой же код с O0:

$ clang++ -O0 -c -emit-llvm main.cpp
$ llvm-dis main.bc

Когда мы смотрим на соответствующую часть, мы видим, что Clang использует getelementptr инструкция для доступа к своему первому (и единственному) элементу данных d:

; Function Attrs: uwtable
define void @_Z3foo8MyDouble(double %d.coerce) #0 {
entry:
%d = alloca %class.MyDouble, align 8
%coerce.dive = getelementptr %class.MyDouble* %d, i32 0, i32 0
store double %d.coerce, double* %coerce.dive, align 1
%d1 = getelementptr inbounds %class.MyDouble* %d, i32 0, i32 0
%0 = load double* %d1, align 8
%call = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([5 x i8]* @.str, i32 0, i32 0), double %0)
ret void
}
1

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector