Recientemente tuve una entrevista y una pregunta que se hizo fue para qué sirve el extern "C"
código C ++. Respondí que es para usar funciones C en código C ++ ya que C no usa el cambio de nombre. Me preguntaron por qué C no usa el cambio de nombre y, para ser sincero, no pude responder.
Entiendo que cuando el compilador de C ++ compila funciones, le da un nombre especial a la función principalmente porque podemos tener funciones sobrecargadas del mismo nombre en C ++ que deben resolverse en el momento de la compilación. En C, el nombre de la función permanecerá igual, o tal vez con un _ antes.
Mi consulta es: ¿qué hay de malo en permitir que el compilador de C ++ también altere las funciones de C? Asumiría que no importa qué nombres les dé el compilador. Llamamos funciones de la misma manera en C y C ++.
fuente
extern "C"
dice que destrozar el nombre de la misma manera que "el" compilador de C lo haría.Respuestas:
Más o menos fue respondida anteriormente, pero intentaré poner las cosas en contexto.
Primero, C vino primero. Como tal, lo que hace C es, más o menos, el "predeterminado". No destruye nombres porque simplemente no lo hace. Un nombre de función es un nombre de función. Un global es un global, y así sucesivamente.
Entonces apareció C ++. C ++ quería poder usar el mismo enlazador que C, y poder enlazar con el código escrito en C. Pero C ++ no podía dejar el C "destrozando" (o falta de) como está. Mira el siguiente ejemplo:
En C ++, estas son funciones distintas, con cuerpos distintos. Si ninguno de ellos está destrozado, ambos se denominarán "función" (o "_función") y el vinculador se quejará de la redefinición de un símbolo. La solución de C ++ consistía en destrozar los tipos de argumentos en el nombre de la función. Entonces, uno se llama
_function_int
y el otro se llama_function_void
(no es un esquema de cambio real) y se evita la colisión.Ahora nos queda un problema. Si
int function(int a)
se definió en un módulo C, y simplemente tomamos su encabezado (es decir, declaración) en código C ++ y lo usamos, el compilador generará una instrucción para que el vinculador lo importe_function_int
. Cuando se definió la función, en el módulo C, no se llamaba así. Fue llamado_function
. Esto provocará un error de vinculador.Para evitar ese error, durante la declaración de la función, le decimos al compilador que es una función diseñada para ser vinculada o compilada por un compilador de C:
El compilador de C ++ ahora sabe importar
_function
más que_function_int
, y todo está bien.fuente
-std=c++11
, por ejemplo , y evitar el uso de cualquier cosa fuera del estándar. Es lo mismo que declarar una versión de Java (aunque las versiones más recientes de Java son compatibles con versiones anteriores). No es culpa de los estándares que las personas usan extensiones específicas del compilador y código dependiente de la plataforma. Por otro lado, no puedes culparlos, ya que hay muchas cosas (especialmente IO, como enchufes) que faltan en el estándar. El comité parece estar poniéndose al día lentamente. Corrígeme si me perdí algo.No es que "no puedan", no lo son , en general.
Si desea llamar a una función en una biblioteca de C llamada
foo(int x, const char *y)
, no es bueno dejar que su compilador de C ++ lo manipulefoo_I_cCP()
(o lo que sea, simplemente creó un esquema de manipulación en el lugar aquí) simplemente porque puede.Ese nombre no se resolverá, la función está en C y su nombre no depende de su lista de tipos de argumentos. Por lo tanto, el compilador de C ++ tiene que saber esto y marcar esa función como C para evitar hacer el cambio.
Recuerde que dicha función C podría estar en una biblioteca cuyo código fuente no tiene, todo lo que tiene es el binario precompilado y el encabezado. Por lo tanto, su compilador de C ++ no puede hacer "lo suyo", no puede cambiar lo que hay en la biblioteca después de todo.
fuente
extern "C"
:)Ya no serían funciones de C.
Una función no es solo una firma y una definición; cómo funciona una función está determinada en gran medida por factores como la convención de llamada. La "Interfaz binaria de aplicación" especificada para su uso en su plataforma describe cómo los sistemas se comunican entre sí. El ABI de C ++ en uso por su sistema especifica un esquema de cambio de nombre, para que los programas en ese sistema sepan cómo invocar funciones en bibliotecas, etc. (Lea el C ++ Itanium ABI para ver un excelente ejemplo. Muy rápidamente verá por qué es necesario).
Lo mismo se aplica para el C ABI en su sistema. Algunas C ABI realmente tienen un esquema de cambio de nombre (por ejemplo, Visual Studio), por lo que se trata menos de "desactivar el cambio de nombre" y más sobre cambiar de C ++ ABI a C ABI, para ciertas funciones. Marcamos las funciones de C como funciones de C, para lo cual el C ABI (en lugar del C ++ ABI) es pertinente. La declaración debe coincidir con la definición (ya sea en el mismo proyecto o en alguna biblioteca de terceros), de lo contrario, la declaración no tiene sentido. Sin eso, su sistema simplemente no sabrá cómo localizar / invocar esas funciones.
En cuanto a por qué las plataformas no definen las ABI de C y C ++ como iguales y se deshacen de este "problema", eso es parcialmente histórico: las ABI de C originales no fueron suficientes para C ++, que tiene espacios de nombres, clases y sobrecarga de operadores, todo de los cuales deben representarse de alguna manera en el nombre de un símbolo de una manera amigable con la computadora, pero también se podría argumentar que hacer que los programas C ahora cumplan con C ++ es injusto para la comunidad C, que tendría que soportar una enorme complejidad ABI solo por el bien de otras personas que desean interoperabilidad.
fuente
+int(PI/3)
, pero con un grano de sal: sería muy cauteloso al hablar de "C ++ ABI" ... AFAIK, hay intentos de definir ABI de C ++, pero no hay estándares reales de facto / de jure , como isocpp.org/files /papers/n4028.pdf declara (y estoy totalmente de acuerdo), cita, es profundamente irónico que C ++ siempre haya apoyado una forma de publicar una API con un ABI binario estable, recurriendo al subconjunto C de C ++ a través de "C" externo ". .C++ Itanium ABI
es solo eso: algo de C ++ ABI para Itanium ... como se discutió en stackoverflow.com/questions/7492180/c-abi-issues-listDe hecho , MSVC destroza los nombres de C, aunque de una manera simple. A veces se agrega
@4
u otro número pequeño. Esto se relaciona con las convenciones de llamadas y la necesidad de limpieza de la pila.Entonces, la premisa es simplemente defectuosa.
fuente
_
?/Gd, /Gr, /Gv, /Gz
. (Es decir, la convención de llamada estándar es lo que se usa a menos que una declaración de función especifique explícitamente una convención de llamada). Estás pensando__cdecl
cuál es la convención de llamadas estándar predeterminada.Es muy común tener programas que están parcialmente escritos en C y parcialmente escritos en algún otro lenguaje (a menudo lenguaje ensamblador, pero a veces Pascal, FORTRAN u otra cosa). También es común que los programas contengan diferentes componentes escritos por diferentes personas que pueden no tener el código fuente para todo.
En la mayoría de las plataformas, hay una especificación, a menudo llamada ABI [Application Binary Interface] que describe lo que debe hacer un compilador para producir una función con un nombre particular que acepte argumentos de algunos tipos particulares y devuelva un valor de algún tipo particular. En algunos casos, un ABI puede definir más de una "convención de llamada"; Los compiladores para tales sistemas a menudo proporcionan un medio para indicar qué convención de llamada se debe utilizar para una función particular. Por ejemplo, en Macintosh, la mayoría de las rutinas de Toolbox usan la convención de llamadas Pascal, por lo que el prototipo de algo como "LineTo" sería algo como:
Si todo el código en un proyecto fue compilado usando el mismo compilador, no importaría qué nombre exportó el compilador para cada función, pero en muchas situaciones será necesario que el código C invoque funciones que fueron compiladas usando otras herramientas y no se puede volver a compilar con el compilador actual [y es muy posible que ni siquiera esté en C]. Ser capaz de definir el nombre del enlazador es, por lo tanto, crítico para el uso de tales funciones.
fuente
Agregaré otra respuesta, para abordar algunas de las discusiones tangenciales que tuvieron lugar.
El C ABI (interfaz binaria de la aplicación) originalmente solicitó pasar argumentos en la pila en orden inverso (es decir, empujado de derecha a izquierda), donde la persona que llama también libera el almacenamiento de la pila. El ABI moderno en realidad usa registros para pasar argumentos, pero muchas de las consideraciones de cambio se remontan a la aprobación del argumento de pila original.
El ABI Pascal original, por el contrario, empujó los argumentos de izquierda a derecha, y el destinatario tuvo que reventar los argumentos. El C ABI original es superior al Pascal ABI original en dos puntos importantes. El orden de inserción de argumentos significa que siempre se conoce el desplazamiento de la pila del primer argumento, lo que permite funciones que tienen un número desconocido de argumentos, donde los primeros argumentos controlan cuántos otros argumentos hay (ala
printf
).La segunda forma en que el C ABI es superior es el comportamiento en caso de que la persona que llama y la persona que llama no estén de acuerdo sobre cuántos argumentos hay. En el caso C, siempre y cuando no acceda a argumentos más allá del último, no pasa nada malo. En Pascal, se saca una cantidad incorrecta de argumentos de la pila y se corrompe toda la pila.
El ABI original de Windows 3.1 estaba basado en Pascal. Como tal, usó el ABI de Pascal (argumentos en orden de izquierda a derecha, pops callee). Dado que cualquier discrepancia en el número de argumento podría conducir a la corrupción de la pila, se formó un esquema de distribución. Cada nombre de función fue destrozado con un número que indica el tamaño, en bytes, de sus argumentos. Entonces, en una máquina de 16 bits, la siguiente función (sintaxis C):
Fue destrozado
function@2
porqueint
tiene dos bytes de ancho. Esto se hizo para que si la declaración y la definición no coinciden, el vinculador no podrá encontrar la función en lugar de dañar la pila en tiempo de ejecución. Por el contrario, si el programa se vincula, puede estar seguro de que el número correcto de bytes aparece de la pila al final de la llamada.Windows de 32 bits en adelante usa el
stdcall
ABI en su lugar. Es similar al Pascal ABI, excepto que el orden de inserción es como en C, de derecha a izquierda. Al igual que el ABI de Pascal, el cambio de nombre altera el tamaño del byte de argumentos en el nombre de la función para evitar la corrupción de la pila.A diferencia de las afirmaciones hechas aquí, el C ABI no destruye los nombres de las funciones, incluso en Visual Studio. Por el contrario, las funciones de manipulación decoradas con la
stdcall
especificación ABI no son exclusivas de VS. GCC también admite este ABI, incluso cuando compila para Linux. Wine lo utiliza ampliamente , ya que utiliza su propio cargador para permitir la vinculación en tiempo de ejecución de archivos binarios compilados de Linux a archivos DLL compilados de Windows.fuente
Los compiladores de C ++ usan el cambio de nombre para permitir nombres de símbolos únicos para funciones sobrecargadas cuya firma de otro modo sería la misma. Básicamente, también codifica los tipos de argumentos, lo que permite el polimorfismo en un nivel basado en funciones.
C no requiere esto ya que no permite la sobrecarga de funciones.
Tenga en cuenta que el cambio de nombre es una (¡pero ciertamente no la única!) Razón por la que no se puede confiar en un 'C ++ ABI'.
fuente
C ++ quiere poder interoperar con el código C que enlaza contra él, o que enlaza contra.
C espera nombres de funciones sin nombre mutilado.
Si C ++ lo destrozara, no encontraría las funciones no destrozadas exportadas de C, o C no encontraría las funciones exportadas de C ++. El enlazador C debe obtener el nombre que espera, ya que no sabe si viene o va a C ++.
fuente
Al modificar los nombres de las funciones y variables de C, se podrían verificar sus tipos en el momento del enlace. Actualmente, todas las implementaciones de (?) C le permiten definir una variable en un archivo y llamarlo como una función en otro. O puede declarar una función con una firma incorrecta (por ejemplo,
void fopen(double)
y luego llamarla.Propuse un esquema para el enlace de tipo seguro de las variables y funciones de C mediante el uso de la manipulación en 1991. El esquema nunca se adoptó, porque, como otros han señalado aquí, esto destruiría la compatibilidad con versiones anteriores.
fuente