Подтвердить что ты не робот

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

#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++ или есть причина для разницы?

4b9b3361

Ответ 1

Во-первых, большой вопрос.

EDIT:

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

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

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

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

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

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

Компиляция:

#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 = С++ 11 -O3 -статический -Wa, -alh test.cpp -o test

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

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

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

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

Вот что мне больше всего подходит для сборки 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 передается как аргумент шаблона, иначе почему вы проверяете его размер()?).

#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>, поэтому для размещения новых по крайней мере можно изменить время ссылки (это слабый символ) - другие специальные, а их конечные объекты находятся в libstdС++ (опять же, по умолчанию они связаны динамически).

Ответ 2

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

Для вопроса, почему clang удаляет код, возможно, что в libС++ конструктор std::array в inlined, поэтому оптимизатор может видеть, что побочных эффектов нет, поэтому необходимо сохранить код.

Ответ 3

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

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