Estoy trabajando en un proyecto que tiene mucho código C heredado . Comenzamos a escribir en C ++, con la intención de convertir también el código heredado. Estoy un poco confundido acerca de cómo interactúan C y C ++. Entiendo que al envolver el código C con extern "C"
el compilador de C ++ no alterará los nombres del código C , pero no estoy completamente seguro de cómo implementar esto.
Entonces, en la parte superior de cada archivo de encabezado C (después de incluir guardias), tenemos
#ifdef __cplusplus
extern "C" {
#endif
y abajo escribimos
#ifdef __cplusplus
}
#endif
Entre los dos, tenemos todos nuestros incluye, typedefs, y prototipos de funciones. Tengo algunas preguntas para ver si entiendo esto correctamente:
Si tengo un archivo C ++ A.hh que incluye un archivo de encabezado C Bh, incluye otro archivo de encabezado C Ch, ¿cómo funciona? Creo que cuando el compilador ingrese a Bh,
__cplusplus
se definirá, por lo que envolverá el código conextern "C"
(y__cplusplus
no se definirá dentro de este bloque). Entonces, cuando entra en Ch,__cplusplus
no se definirá y el código no se envolveráextern "C"
. ¿Es esto correcto?¿Hay algo malo en envolver un fragmento de código
extern "C" { extern "C" { .. } }
? ¿Qué hará el segundoextern "C"
?No colocamos este contenedor alrededor de los archivos .c, solo los archivos .h. Entonces, ¿qué sucede si una función no tiene un prototipo? ¿El compilador piensa que es una función de C ++?
También estamos usando un código de terceros que está escrito en C y no tiene este tipo de envoltorio. Cada vez que incluyo un encabezado de esa biblioteca, pongo un
extern "C"
alrededor del #include. ¿Es esta la forma correcta de lidiar con eso?Finalmente, ¿es esto una buena idea? ¿Hay algo más que debamos hacer? Vamos a mezclar C y C ++ en el futuro previsible, y quiero asegurarme de que cubrimos todas nuestras bases.
fuente
To ensure that the names declared in that portion of code have C linkage, and thus C++ name mangling is not performed.
(Lo obtuve del enlace )Respuestas:
extern "C"
realmente no cambia la forma en que el compilador lee el código. Si su código está en un archivo .c, se compilará como C, si está en un archivo .cpp, se compilará como C ++ (a menos que haga algo extraño en su configuración).Lo que
extern "C"
sí afecta es la vinculación. Las funciones de C ++, cuando se compilan, tienen sus nombres destrozados; esto es lo que hace posible la sobrecarga. El nombre de la función se modifica en función de los tipos y el número de parámetros, de modo que dos funciones con el mismo nombre tendrán nombres de símbolos diferentes.El código dentro de un
extern "C"
sigue siendo código C ++. Existen limitaciones sobre lo que puede hacer en un bloque "C" externo, pero se trata de vinculación. No puede definir ningún símbolo nuevo que no pueda construirse con el enlace C. Eso significa que no hay clases o plantillas, por ejemplo.extern "C"
Los bloques anidan bien. También existeextern "C++"
si te encuentras atrapado irremediablemente dentro de lasextern "C"
regiones, pero no es una buena idea desde una perspectiva de limpieza.Ahora, específicamente con respecto a sus preguntas numeradas:
Con respecto al n. ° 1: __cplusplus permanecerá definido dentro de los
extern "C"
bloques. Sin embargo, esto no importa, ya que los bloques deben anidar perfectamente.Con respecto al n. ° 2: __cplusplus se definirá para cualquier unidad de compilación que se ejecute a través del compilador de C ++. En general, eso significa archivos .cpp y cualquier archivo incluido por ese archivo .cpp. El mismo .h (o .hh o .hpp o what-have-you) podría interpretarse como C o C ++ en diferentes momentos, si las incluyen diferentes unidades de compilación. Si desea que los prototipos en el archivo .h hagan referencia a los nombres de los símbolos C, entonces deben tenerlos
extern "C"
cuando se interpretan como C ++, y no deberían tenerlosextern "C"
cuando se interpretan como C, de ahí la#ifdef __cplusplus
comprobación.Para responder a su pregunta # 3: las funciones sin prototipos tendrán un enlace C ++ si están en archivos .cpp y no dentro de un
extern "C"
bloque. Sin embargo, esto está bien, porque si no tiene un prototipo, solo puede ser llamado por otras funciones en el mismo archivo, y entonces generalmente no le importa cómo se ve el enlace, porque no planea tener esa función ser llamado por cualquier cosa fuera de la misma unidad de compilación de todos modos.Para el # 4, lo tienes exactamente. Si incluye un encabezado para el código que tiene un enlace C (como el código que fue compilado por un compilador de C), entonces debe
extern "C"
el encabezado, de esa manera podrá vincular con la biblioteca. (De lo contrario, su enlazador estaría buscando funciones con nombres como_Z1hic
cuando estaba buscandovoid h(int, char)
5: Este tipo de mezcla es una razón común para usar
extern "C"
, y no veo nada malo en hacerlo de esta manera, solo asegúrate de entender lo que estás haciendo.fuente
extern "C++"
cuando su encabezado / código C ++ está atrapado en el fondo de algún código C__cplusplus
que es para determinar siC++
se está utilizando contraC
, por lo que define de forma manual / desafía explícitamente el objetivo de la misma ...extern "C"
no cambia la presencia o ausencia de la__cplusplus
macro. Simplemente cambia el enlace y el cambio de nombre de las declaraciones envueltas.Puedes anidar
extern "C"
bloques muy felizmente.Si compila sus
.c
archivos como C ++, todo lo que no esté en unextern "C"
bloque y sin unextern "C"
prototipo se tratará como una función de C ++. Si los compila como C, por supuesto, todo será una función C.si
Puede mezclar C y C ++ de forma segura de esta manera.
fuente
.c
archivos como C ++, entonces todo se compila como código C ++, incluso si está en unextern "C"
bloque. Elextern "C"
código no puede usar características que dependen de convenciones de llamadas de C ++ (por ejemplo, sobrecarga del operador), pero el cuerpo de la función todavía se compila como C ++, con todo lo que eso conlleva.Un par de trucos que son colorarios de la excelente respuesta de Andrew Shelansky y con los que no está de acuerdo un poco no cambia realmente la forma en que el compilador lee el código
Debido a que sus prototipos de funciones se compilan como C, no puede tener una sobrecarga de los mismos nombres de funciones con diferentes parámetros; esa es una de las características clave del cambio de nombre del compilador. Se describe como un problema de vinculación, pero eso no es del todo cierto: obtendrá errores tanto del compilador como del vinculador.
Los errores del compilador se producirán si intenta utilizar las características de C ++ de la declaración de prototipo, como la sobrecarga.
Los errores del enlazador ocurrirán más tarde porque su función parecerá que no se encuentra, si no tiene el contenedor externo "C" alrededor de las declaraciones y el encabezado se incluye en una mezcla de fuente C y C ++.
Una razón para desalentar a las personas de usar la compilación C como configuración de C ++ es porque esto significa que su código fuente ya no es portátil. Esa configuración es una configuración de proyecto y, por lo tanto, si un archivo .c se coloca en otro proyecto, no se compilará como c ++. Prefiero que la gente se tome el tiempo de cambiar el nombre de los sufijos de archivo a .cpp.
fuente
Se trata de ABI, para permitir que las aplicaciones C y C ++ usen interfaces C sin ningún problema.
Dado que el lenguaje C es muy fácil, la generación de código fue estable durante muchos años para diferentes compiladores, como GCC, Borland C \ C ++, MSVC, etc.
Si bien C ++ se vuelve cada vez más popular, se deben agregar muchas cosas al nuevo dominio de C ++ (por ejemplo, finalmente, Cfront fue abandonado en AT&T porque C no pudo cubrir todas las características que necesita). Como la función de plantilla y la generación de código en tiempo de compilación, los diferentes proveedores de compiladores en realidad hicieron la implementación real del compilador y el enlazador C ++ por separado, las ABI reales no son compatibles en absoluto con el programa C ++ en diferentes plataformas.
Es posible que a la gente todavía le guste implementar el programa real en C ++, pero aún mantener la interfaz C antigua y ABI como de costumbre, el archivo de encabezado debe declarar "C" externa {} , le dice al compilador generar C ABI compatible / antiguo / simple / fácil para las funciones de interfaz si el compilador es el compilador C no el compilador C ++.
fuente