#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 ++ или есть причина для разницы?
Во-первых, отличный вопрос.
РЕДАКТИРОВАТЬ:
Я не очень правильно прочитал ваш код с первого раза. В вашем коде есть важный внешний вызов. Это в этой инструкции 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 ++ (опять же, они динамически связаны по умолчанию).
Потому что могут быть побочные эффекты в конструкторе std::array
, Однако, поскольку g ++ и clang не используют одну и ту же стандартную библиотеку (libstdc ++ для g ++ и libc ++ для clang).
На вопрос, почему Clang удаляет код, возможно, что в libc ++ std::array
Это конструктор встроенный, поэтому оптимизатор может видеть, что нет побочных эффектов, поэтому необходимо сохранить код.
Это может произойти по ряду причин. Возможно, по какой-то причине gcc не встроен так же хорошо, как лязг. Может помочь поворот ручек на gcc. Или, возможно, что-то еще происходит, например, проблема псевдонимов, которую gcc по какой-то причине не может решить. Невозможно узнать, не проследив через gcc, чтобы узнать подробности.
В итоге, просто два разных компилятора выполняют разные преобразования кода. GCC может быть расширен, чтобы покрыть этот случай.