una todas tipos programas llamar las funciones funcion ejemplos completo como comandos c++ c name-mangling extern-c

c++ - todas - ¿Por qué las funciones C no se pueden alterar por nombre?



tipos de funciones en c (9)

¿Qué tiene de malo permitir que el compilador de C ++ altere también las funciones de 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 C ++ ABI en uso por su sistema especifica un esquema de cambio de nombre, de modo 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 eran 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.

Recientemente tuve una entrevista y una pregunta que se hizo fue para qué sirve la extern "C" en el código C ++. Respondí que es 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 ++.


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 Pascal ABI original, por el contrario, empujó los argumentos de izquierda a derecha, y el destinatario tuvo que hacer estallar 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 Windows 3.1 ABI original estaba basado en Pascal. Como tal, utilizó 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):

int function(int a)

Fue destrozado para function@2 , porque int 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 destroza los nombres de las funciones, incluso en Visual Studio. Por el contrario, las funciones de manipulación decoradas con la especificación stdcall ABI no son exclusivas de VS. GCC también admite este ABI, incluso cuando compila para Linux. Wine 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.


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 reorganización en 1991. El esquema nunca se adoptó, porque, como otros han señalado aquí, esto destruiría la compatibilidad con versiones anteriores.


C ++ quiere poder interoperar con el código C que enlaza contra él o con el que enlaza.

C espera nombres de funciones sin nombre mutilado.

Si C ++ lo destrozara, no encontraría las funciones no destrozadas exportadas desde C, o C no encontraría las funciones exportadas por C ++. El enlazador C debe obtener el nombre que espera, porque no sabe si proviene o va a C ++.


De hecho , MSVC destroza los nombres de C, aunque de manera simple. A veces 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.


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 [Interfaz binaria de aplicación] 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 utilizan la convención de llamadas Pascal, por lo que el prototipo de algo como "LineTo" sería algo así como:

/* Note that there are no underscores before the "pascal" keyword because the Toolbox was written in the early 1980s, before the Standard and its underscore convention were published */ pascal void LineTo(short x, short y);

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 llame a 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.


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''.


Más o menos fue respondida anteriormente, pero trataré de 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:

int function(int a); int function();

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 ++ fue manglear 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 al enlazador para importar _function_int . Cuando se definió la función, en el módulo C, no se llamaba así. Se llamaba _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:

extern "C" int function(int a);

El compilador de C ++ ahora sabe importar _function lugar de _function_int , y todo está bien.


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 foo_I_cCP() en foo_I_cCP() (o lo que sea, simplemente creó un esquema de manipulación en el lugar aquí ) solo 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. Entonces, su compilador de C ++ no puede hacer "lo suyo", no puede cambiar lo que hay en la biblioteca después de todo.