Combinando C ++ y C: ¿cómo funciona #ifdef __cplusplus?

319

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:

  1. 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, __cplusplusse definirá, por lo que envolverá el código con extern "C" (y __cplusplusno se definirá dentro de este bloque). Entonces, cuando entra en Ch, __cplusplusno se definirá y el código no se envolverá extern "C". ¿Es esto correcto?

  2. ¿Hay algo malo en envolver un fragmento de código extern "C" { extern "C" { .. } }? ¿Qué hará el segundo extern "C" ?

  3. 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 ++?

  4. 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?

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

dublev
fuente
2
De manera concisa, esta es la mejor explicación: 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 )
anhldbk
No tiene que poner el nombre del lenguaje C en negrita
Edward Karak

Respuestas:

290

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 existe extern "C++"si te encuentras atrapado irremediablemente dentro de las extern "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 tenerlos extern "C"cuando se interpretan como C, de ahí la #ifdef __cpluspluscomprobació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 _Z1hiccuando 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.

Andrew Shelansky
fuente
10
Bueno para mencionar extern "C++"cuando su encabezado / código C ++ está atrapado en el fondo de algún código C
deddebme
1
Escribí un simple programa en C. Dentro de él agregué el bloque #ifdef __cplusplus y agregué un printf ("__ cplusplus definido \ n"); en eso. Si lo compilo con gcc, "__cplusplus definido" no se imprime, pero si lo compilo con g ++, se imprime. Así que calculo que __cplusplus significa que el compilador es compilador de C ++ (usted lo dijo) ¿No es correcto? (porque te vi diciendo '__cplusplus debería definirse dentro de los bloques externos' C '. ¿Podemos definir __cplusplus explícitamente?
Chan Kim,
1
Mientras que usted debe ser capaz de definir (casi) todo lo que quieras, todo el punto de __cplusplusque es para determinar si C++se está utilizando contra C, por lo que define de forma manual / desafía explícitamente el objetivo de la misma ...
nurchi
La "C" externa no se trata de cómo ve el compilador el archivo fuente, sino de cómo ve el archivo de encabezado. Las estructuras pueden tener diferentes tamaños cuando se compilan como C frente a C ++, por supuesto, existe el cambio de nombre y posiblemente otras diferencias.
Nick
39
  1. extern "C"no cambia la presencia o ausencia de la __cplusplusmacro. Simplemente cambia el enlace y el cambio de nombre de las declaraciones envueltas.

  2. Puedes anidar extern "C"bloques muy felizmente.

  3. Si compila sus .carchivos como C ++, todo lo que no esté en un extern "C"bloque y sin un extern "C"prototipo se tratará como una función de C ++. Si los compila como C, por supuesto, todo será una función C.

  4. si

  5. Puede mezclar C y C ++ de forma segura de esta manera.

Anthony Williams
fuente
Si compila .carchivos como C ++, entonces todo se compila como código C ++, incluso si está en un extern "C"bloque. El extern "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.
David C.
21

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.

Andy Dent
fuente
1
Esta fue la causa críptica, arrancando mi cabello. Realmente necesita ser publicado en algún lugar.
Mitchell Currie
3

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

Bo Zhou
fuente