Что мешает g ++ устранить временный массив std ::, не используемый во время выполнения?

#include <array>
#include <cassert>

class P {
public:
P() : _value(nullptr) {}
~P() { delete _value; }

private:
char *_value;
};

void foo() {
if(std::array<P, 4>().size() != 4)
assert(false);
}

Функция foo() создает временный массив для проверки размера, который ожидал программист. С -O1 или выше G ++ выясняет assert не подведет и вызов __assert_fail удаляется из сгенерированного кода. Но g ++ все еще генерирует код, чтобы сначала создать, а затем уничтожить теперь неиспользуемый массив.

g++ -std=c++11 -O3 [4.8.2]:

0000000000000000 <_Z3foov>:1
0:       55                      push   %rbp1
1:       66 0f ef c0             pxor   %xmm0,%xmm01
5:       53                      push   %rbx1
6:       48 83 ec 28             sub    $0x28,%rsp1
a:       66 0f 7f 04 24          movdqa %xmm0,(%rsp)1
f:       48 8d 5c 24 20          lea    0x20(%rsp),%rbx1
14:       48 89 e5                mov    %rsp,%rbp1
17:       66 0f 7f 44 24 10       movdqa %xmm0,0x10(%rsp)1
1d:       0f 1f 00                nopl   (%rax)1
20:       48 83 eb 08             sub    $0x8,%rbx1
24:       48 8b 3b                mov    (%rbx),%rdi1
27:       e8 00 00 00 00          callq  2c <_Z3foov+0x2c>1
2c:       48 39 eb                cmp    %rbp,%rbx1
2f:       75 ef                   jne    20 <_Z3foov+0x20>1
31:       48 83 c4 28             add    $0x28,%rsp1
35:       5b                      pop    %rbx1
36:       5d                      pop    %rbp1
37:       c3                      retq   1

clang, с другой стороны, удаляет весь код, кроме оператора return.

clang -std=c++11 -O3:

0000000000000000 <_Z3foov>:1
0:       c3                      retq   1

Просто невезение с g ++ или есть причина для разницы?

28

Решение

Во-первых, отличный вопрос.

РЕДАКТИРОВАТЬ:

Я не очень правильно прочитал ваш код с первого раза. В вашем коде есть важный внешний вызов. Это в этой инструкции e8 00 00 00 00 callq 2c <_Z3foov+0x2c>1, Он не вызывает адрес впереди себя — вместо этого его цель будет заменена во время соединения. Вот как работает связывание — в файле эльфа будет сказано: «такая-то и такая инструкция разрешит эту цель во время ссылки». Ассемблер не был перечислен полностью, и поэтому мы не знаем адрес этого звонка. Предположительно это delete _value в коде, однако libstdc ++ (с delet и т. д.) динамически связанный по умолчанию. Вы можете изменить это, используя мои флаги компилятора, или сделать свой листинг внутри GDB (т.е. после ссылки).

Вернуться к ответу:

Это особый случай, когда программисты gcc сделали выбор не оптимизировать. Причина в том, что операторы new и delete помечаются как «слабые символы», и компоновщик будет искать альтернативы, выбирая предоставленного пользователя или отступая, если ничего не найдено.

Вот обсуждение обоснования этого. Эти операторы предназначены для глобальной замены, и слабое связывание является одним из решений.

Статическое связывание не меняет этого, потому что free и malloc в glibc все еще могут быть изменены во время компоновки.

Статическое связывание или использование оптимизации времени соединения должен использовать встроенное удаление, Однако в этом случае, если вы вместо этого статически связаны, шанс все еще упущен. Однако в вашем первоначальном примере такой возможности нет.

Компилируя это:

#include <array>
#include <cassert>
#include <cstdlib>

void * operator new(std::size_t n) throw(std::bad_alloc)
{
return malloc(n);
}
void operator delete(void * p) throw()
{
if(p != nullptr)
free(p);
}

class P {
public:
P() : _value(nullptr) {}
~P() { delete _value; }

private:
char *_value;
};

void foo() {
if(std::array<P, 4>().size() != 4)
assert(false);
}

int main(){
foo();
}

с этим;
g ++ -std = c ++ 11 -O3 -static -Wa, -alh test.cpp -o test

ссылки на libstdc ++ / glibc статически, так что он должен знать, что такое free и malloc, однако он не совсем понимает, что foo тривиален.

Тем не мение, nm -gC test | grep free производит слабый символ __free_hook, который я нашел объяснение Вот. Таким образом, поведение free и malloc (и, следовательно, оператора new и delete) на самом деле всегда можно изменить время ссылки при компиляции с gcc. Вот что мешает оптимизации — досадно -fno-weak оставляет эти символы там.

Вышеприведенный абзац верен, но является результатом пропущенной оптимизации, а не причиной, чтобы избежать оптимизации.

При оптимизации времени соединения возможно, что gcc можно убедить выполнить эту оптимизацию, но сначала вы должны собрать все остальное с помощью -flto, включая libstdc ++.

Вот что делает gcc для меня при статическом построении примера:

Dump of assembler code for function _Z3foov:
0x08048ef0 <+0>:    push   %esi
0x08048ef1 <+1>:    push   %ebx
0x08048ef2 <+2>:    sub    $0x24,%esp
0x08048ef5 <+5>:    movl   $0x0,0x10(%esp)
0x08048efd <+13>:   lea    0x20(%esp),%ebx
0x08048f01 <+17>:   movl   $0x0,0x14(%esp)
0x08048f09 <+25>:   lea    0x10(%esp),%esi
0x08048f0d <+29>:   movl   $0x0,0x18(%esp)
0x08048f15 <+37>:   movl   $0x0,0x1c(%esp)
0x08048f1d <+45>:   lea    0x0(%esi),%esi
0x08048f20 <+48>:   sub    $0x4,%ebx
0x08048f23 <+51>:   mov    (%ebx),%eax
0x08048f25 <+53>:   test   %eax,%eax
0x08048f27 <+55>:   je     0x8048f31 <_Z3foov+65>
0x08048f29 <+57>:   mov    %eax,(%esp)
0x08048f2c <+60>:   call   0x804dea0 <free>
0x08048f31 <+65>:   cmp    %esi,%ebx
0x08048f33 <+67>:   jne    0x8048f20 <_Z3foov+48>
0x08048f35 <+69>:   add    $0x24,%esp
0x08048f38 <+72>:   pop    %ebx
0x08048f39 <+73>:   pop    %esi
0x08048f3a <+74>:   ret

Призыв к свободе никуда не денется.

Если это проблема, оптимизируйте ее самостоятельно. Шаблоны делают это легко (при условии, что std :: array передается как аргумент шаблона, иначе зачем вы проверяете его size ()?).

#include <array>
#include <cassert>

class P {
public:
P() : _value(nullptr) {}
~P() { delete _value; }

private:
char *_value;
};

void foo() {
if(std::tuple_size<std::array<P, 4> >::value != 4)
assert(false);
}

int main(){
foo();
}

Код может быть сделан, чтобы молча потерпеть неудачу, если std::array<P, 4> вектор или что-то, и прибегните к вашему методу построения по умолчанию.

nm -C test выходы W operator new(unsigned int, void*) когда я добавил #include <new>Таким образом, размещение нового по крайней мере может быть изменено по времени ссылки (это слабый символ) — остальные являются особыми, и их конечные цели находятся в libstdc ++ (опять же, они динамически связаны по умолчанию).

5

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

Потому что могут быть побочные эффекты в конструкторе std::array, Однако, поскольку g ++ и clang не используют одну и ту же стандартную библиотеку (libstdc ++ для g ++ и libc ++ для clang).

На вопрос, почему Clang удаляет код, возможно, что в libc ++ std::arrayЭто конструктор встроенный, поэтому оптимизатор может видеть, что нет побочных эффектов, поэтому необходимо сохранить код.

0

Это может произойти по ряду причин. Возможно, по какой-то причине gcc не встроен так же хорошо, как лязг. Может помочь поворот ручек на gcc. Или, возможно, что-то еще происходит, например, проблема псевдонимов, которую gcc по какой-то причине не может решить. Невозможно узнать, не проследив через gcc, чтобы узнать подробности.

В итоге, просто два разных компилятора выполняют разные преобразования кода. GCC может быть расширен, чтобы покрыть этот случай.

0
По вопросам рекламы [email protected]