¿Por qué la asignación inicial de C ++ es mucho mayor que la de C?

138

Cuando se usa el mismo código, simplemente cambiando el compilador (de un compilador C a un compilador C ++) cambiará la cantidad de memoria asignada. No estoy muy seguro de por qué es así y me gustaría entenderlo más. Hasta ahora, la mejor respuesta que he recibido es "probablemente las transmisiones de E / S", que no es muy descriptiva y me hace preguntarme sobre el aspecto "no paga por lo que no usa" de C ++.

Estoy usando los compiladores Clang y GCC, versiones 7.0.1-8 y 8.3.0-6 respectivamente. Mi sistema se ejecuta en Debian 10 (Buster), la última. Los puntos de referencia se realizan a través del macizo Valgrind.

#include <stdio.h>

int main() {
    printf("Hello, world!\n");
    return 0;
}

El código utilizado no cambia, pero si compilo como C o como C ++, cambia los resultados del benchmark Valgrind. Sin embargo, los valores permanecen consistentes entre los compiladores. Las asignaciones de tiempo de ejecución (pico) para el programa son las siguientes:

  • CCG (C): 1,032 bytes (1 KB)
  • G ++ (C ++): 73,744 bytes, (~ 74 KB)
  • Clang (C): 1,032 bytes (1 KB)
  • Clang ++ (C ++): 73,744 bytes (~ 74 KB)

Para compilar, uso los siguientes comandos:

clang -O3 -o c-clang ./main.c
gcc -O3 -o c-gcc ./main.c
clang++ -O3 -o cpp-clang ./main.cpp
g++ -O3 -o cpp-gcc ./main.cpp

Para Valgrind, ejecuto valgrind --tool=massif --massif-out-file=m_compiler_lang ./compiler-langen cada compilador e idioma, luego ms_printpara mostrar los picos.

¿Estoy haciendo algo mal aquí?

Rerumu
fuente
11
Para empezar, ¿cómo estás construyendo? ¿Qué opciones usas? ¿Y cómo lo mides? ¿Cómo ejecutas Valgrind?
Algún tipo programador
17
Si no recuerdo mal, los compiladores modernos de C ++ tienen un modelo de excepción en el que no hay impacto en el rendimiento al ingresar un trybloque a expensas de una huella de memoria más grande, tal vez con una tabla de salto o algo así. Tal vez intente compilar sin excepciones y ver qué impacto tiene. Editar: de hecho, intente deshabilitar iterativamente varias funciones de c ++ para ver qué impacto tiene en la huella de la memoria.
François Andrieux
3
Al compilar en clang++ -xclugar de clang, la misma asignación estaba allí, lo que sugiere que se debe a las bibliotecas vinculadas
Justin
14
@bigwillydos Esto es de hecho C ++, no veo ninguna parte de las especificaciones de C ++ que rompe ... Aparte de incluir potencialmente stdio.h en lugar de cstdio, pero esto está permitido al menos en la versión anterior de C ++. ¿Qué crees que está "malformado" en este programa?
Vality
44
Me parece sospechoso que esos compiladores gcc y clang generen exactamente el mismo número de bytes en Cmodo y exactamente el mismo número de bytes en C++modo. ¿Cometiste un error de transcripción?
RonJohn

Respuestas:

149

El uso del montón proviene de la biblioteca estándar de C ++. Asigna memoria para el uso interno de la biblioteca al inicio. Si no se vincula con él, debería haber una diferencia cero entre la versión C y C ++. Con GCC y Clang, puede compilar el archivo con:

g ++ -Wl, - según sea necesario main.cpp

Esto le indicará al vinculador que no se vincule contra bibliotecas no utilizadas. En su código de ejemplo, la biblioteca de C ++ no se usa, por lo que no debe vincularse con la biblioteca estándar de C ++.

También puede probar esto con el archivo C. Si compila con:

gcc main.c -lstdc ++

El uso del montón volverá a aparecer, aunque haya creado un programa en C.

El uso del montón obviamente depende de la implementación específica de la biblioteca C ++ que está utilizando. En su caso, esa es la biblioteca GNU C ++, libstdc ++ . Otras implementaciones podrían no asignar la misma cantidad de memoria, o puede ser que no se asignará ningún recuerdo en absoluto (al menos no en el arranque.) La biblioteca de LLVM C ++ ( libc ++ ), por ejemplo, no hace asignación del montón en el arranque, por lo menos en mi Linux máquina:

clang ++ -stdlib = libc ++ main.cpp

El uso de almacenamiento dinámico es lo mismo que no vincularse en absoluto contra él.

(Si la compilación falla, probablemente libc ++ no esté instalado. El nombre del paquete generalmente contiene "libc ++" o "libcxx").

Nikos C.
fuente
50
Al ver esta respuesta, mi primer pensamiento es: " Si este indicador ayuda a reducir la sobrecarga innecesaria, ¿por qué no está activado de forma predeterminada? ". ¿Hay una buena respuesta a eso?
Nat
44
@Nat Supongo que en el momento del desarrollo es más lento de compilar. Cuando esté listo para crear una versión de lanzamiento, entonces la encenderá. También en una base de código normal / grande, la diferencia puede ser mínima (si está utilizando una gran cantidad de la biblioteca STD, etc.)
DarcyThomas
24
@Nat El -Wl,--as-neededindicador elimina las bibliotecas que especifique en sus -lindicadores, pero que en realidad no está utilizando. Entonces, si no usa una biblioteca, simplemente no se vincule contra ella. No necesitas esta bandera para esto. Sin embargo, si su sistema de compilación agrega demasiadas bibliotecas y sería mucho trabajo limpiarlas todas y vincular solo las necesarias, entonces puede usar este indicador en su lugar. Sin embargo, la biblioteca estándar es una excepción, ya que está vinculada automáticamente. Entonces este es un caso de esquina.
Nikos C.
36
@Nat: según sea necesario, puede tener efectos secundarios no deseados, funciona comprobando si usa algún símbolo de una biblioteca y elimina aquellos que no pasan la prueba. PERO: una biblioteca también podría hacer varias cosas implícitamente, por ejemplo, si tiene una instancia estática de C ++ en la biblioteca, entonces se llamará automáticamente a su constructor. Hay casos raros en los que es necesaria una biblioteca a la que no llama explícitamente, pero existen.
Norbert Lange
3
@NikosC. Buildsystems no sabe automáticamente qué símbolos usa su aplicación y qué bibliotecas los implementan (varía entre compiladores, archs, distribuciones y bibliotecas c / c ++). Hacer eso correctamente es bastante problemático, al menos para las bibliotecas de tiempo de ejecución base. Pero para los casos raros en los que necesita una biblioteca, simplemente debe usar --no como sea necesario para esa, y dejar - como sea necesario en cualquier otro lugar. Un caso de uso que vi son las bibliotecas para rastrear / depurar (lttng) y las bibliotecas que hacen algo parecido a la autenticación / conexión.
Norbert Lange
16

Ni GCC ni Clang son compiladores, en realidad son programas de controladores de cadena de herramientas. Eso significa que invocan el compilador, el ensamblador y el enlazador.

Si compila su código con un compilador C o C ++, obtendrá el mismo ensamblado producido. El ensamblador producirá los mismos objetos. La diferencia es que el controlador de la cadena de herramientas proporcionará diferentes entradas al enlazador para los dos idiomas diferentes: diferentes inicios (C ++ requiere código para ejecutar constructores y destructores para objetos con una duración de almacenamiento local estático o de subprocesos a nivel de espacio de nombres, y requiere infraestructura para la pila marcos para admitir el desenrollado durante el procesamiento de excepciones, por ejemplo), la biblioteca estándar de C ++ (que también tiene objetos de duración de almacenamiento estático a nivel de espacio de nombres) y probablemente bibliotecas de tiempo de ejecución adicionales (por ejemplo, libgcc con su infraestructura de desenrollado de pila).

En resumen, no es el compilador el que causa el aumento de la huella, es la vinculación de las cosas que ha elegido usar al elegir el lenguaje C ++.

Es cierto que C ++ tiene la filosofía de "paga solo por lo que usas", pero al usar el lenguaje, pagas por ello. Puede deshabilitar partes del lenguaje (RTTI, manejo de excepciones) pero ya no está usando C ++. Como se mencionó en otra respuesta, si no usa la biblioteca estándar, puede indicarle al controlador que la omita (--Wl, - según sea necesario) pero si no va a usar ninguna de las funciones de C ++ o su biblioteca, ¿por qué incluso eliges C ++ como lenguaje de programación?

Stephen M. Webb
fuente
El hecho de que habilitar el manejo de excepciones tenga un costo, incluso si realmente no lo usa, es un problema. Eso no es normal para las características de C ++ en general, y es algo que los grupos de trabajo de estándares de C ++ están tratando de pensar en formas de arreglar. Vea la charla magistral de Herb Sutter en ACCU 2019 Desfragmentando C ++: haciendo que las excepciones sean más asequibles y utilizables . Sin embargo, es un hecho desafortunado en C ++ actual. Y las excepciones tradicionales de C ++ probablemente siempre tendrán ese costo, incluso si / cuando se agregan nuevos mecanismos para nuevas excepciones con una palabra clave.
Peter Cordes