c++ - optimizada - ¿Por qué no se optimiza esta variable no utilizada?
seo url (3)
Jugué con Godbolt''s CompilerExplorer. Quería ver qué buenas son ciertas optimizaciones. Mi ejemplo mínimo de trabajo es:
#include <vector>
int foo() {
std::vector<int> v {1, 2, 3, 4, 5};
return v[4];
}
El ensamblador generado (por clang 5.0.0, -O2 -std = c ++ 14):
foo(): # @foo()
push rax
mov edi, 20
call operator new(unsigned long)
mov rdi, rax
call operator delete(void*)
mov eax, 5
pop rcx
ret
Como se puede ver, clang sabe la respuesta, pero hace un montón de cosas antes de regresar. Me parece que incluso el vector se crea, debido a "operador nuevo / eliminar".
¿Alguien puede explicarme qué pasa aquí y por qué no solo regresa?
El código generado por GCC (no copiado aquí) parece construir el vector explícitamente. ¿Alguien sabe que GCC no es capaz de deducir el resultado?
Como se nota en los comentarios, el operator new
puede ser reemplazado. Esto puede suceder en cualquier Unidad de Traducción. La optimización de un programa para el caso de que no se remplace requiere, por lo tanto, un análisis de todo el programa. Y si es reemplazado, tienes que llamarlo por supuesto.
Si el operator new
predeterminado operator new
es una llamada de E / S de la biblioteca no está especificado. Eso es importante, porque las llamadas de E / S de la biblioteca son observables y, por lo tanto, tampoco pueden optimizarse.
El cambio de N3664 a [expr.new], citado en una respuesta y un comentario, permite que las nuevas expresiones no invoquen una función de asignación global reemplazable. Pero vector
asigna memoria usando std::allocator<T>::allocate
, que llama ::operator new
directamente, no a través de una nueva expresión . De modo que ese permiso especial no se aplica, y generalmente los compiladores no pueden eludir tales llamadas directas a ::operator new
.
Sin embargo, no se pierde la esperanza de que la especificación std::allocator<T>::allocate
tenga this que decir:
Observaciones: el almacenamiento se obtiene llamando al
::operator new
, pero no se especifica cuándo o con qué frecuencia se llama a esta función.
Aprovechando este permiso, el std::allocator
libc ++ utiliza incorporaciones de clang especiales para indicar al compilador que la elisión está permitida. Con -stdlib=libc++
, clang compila tu código a
foo(): # @foo()
mov eax, 5
ret
std::vector<T>
es una clase bastante complicada que implica asignación dinámica. Si bien clang++
veces puede elide las asignaciones de heap , es una optimización bastante complicada y no debe confiar en ella. Ejemplo:
int foo() {
int* p = new int{5};
return *p;
}
foo(): # @foo() mov eax, 5 ret
Como ejemplo, usar std::array<T>
(que no asigna dinámicamente) produce un código completamente en línea :
#include <array>
int foo() {
std::array v{1, 2, 3, 4, 5};
return v[4];
}
foo(): # @foo() mov eax, 5 ret
Como Marc Glisse notó en los comentarios de la otra respuesta, esto es lo que dice el Estándar en [expr.new] # 10 :
Una implementación puede omitir una llamada a una función de asignación global reemplazable ([new.delete.single], [new.delete.array]). Cuando lo hace, el almacenamiento lo proporciona la implementación o se proporciona extendiendo la asignación de otra nueva expresión. La implementación puede extender la asignación de una nueva expresión e1 para proporcionar almacenamiento para una nueva expresión e2 si lo siguiente sería cierto si la asignación no se extendiera: [...]