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

Estoy trabajando en un proyecto que tiene mucho código C heredado. Empezamos a escribir en C ++, con la intención de convertir el código heredado también. 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 comstackdor de C ++ no destruirá los nombres del código C , pero no estoy del todo seguro de cómo implementarlo.

Entonces, en la parte superior de cada archivo de encabezado C (después de los guardias de inclusión), tenemos

 #ifdef __cplusplus extern "C" { #endif 

y en la parte inferior, escribimos

 #ifdef __cplusplus } #endif 

Entre los dos, tenemos todos nuestros includes, typedefs y prototipos de funciones. Tengo algunas preguntas para ver si estoy entendiendo esto correctamente:

  1. Si tengo un archivo C ++ A.hh que incluye un archivo de cabecera C Bh, incluye otro archivo de cabecera C Ch, ¿cómo funciona esto? Creo que cuando el comstackdor __cplusplus en Bh, se __cplusplus , por lo que ajustará el código con extern "C" (y __cplusplus no se definirá dentro de este bloque). Por lo tanto, cuando entra en Ch, __cplusplus no se definirá y el código no se incluirá en la extern "C" . ¿Es esto correcto?

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

  3. No ponemos este envoltorio alrededor de los archivos .c, solo los archivos .h. Entonces, ¿qué sucede si una función no tiene un prototipo? ¿El comstackdor cree 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 a su alrededor. Cada vez que incluyo un encabezado de esa biblioteca, he estado poniendo una extern "C" alrededor del #include. ¿Es esta la manera correcta de lidiar con eso?

  5. Finalmente, ¿es esta una buena idea? ¿Hay algo más que deberíamos hacer? Vamos a mezclar C y C ++ en el futuro previsible, y quiero asegurarme de que cubrimos todas nuestras bases.

extern "C" realmente no cambia la forma en que el comstackdor lee el código. Si su código está en un archivo .c, se comstackrá como C, si está en un archivo .cpp, se comstackrá como C ++ (a menos que haga algo extraño a su configuración).

Lo que hace la extern "C" es afectar la vinculación. Las funciones de C ++, cuando se comstackn, tienen sus nombres mutilados: 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 diferentes nombres de símbolos.

El código dentro de una extern "C" todavía es código C ++. Existen limitaciones sobre lo que puede hacer en un bloque externo “C”, pero se trata de enlaces. No puede definir ningún nuevo símbolo que no se pueda construir con el enlace C. Eso significa que no hay clases o plantillas, por ejemplo.

extern "C" bloques extern "C" anidan muy bien. También existe un extern "C++" si te encuentras irremediablemente atrapado dentro de las regiones extern "C" , pero no es una buena idea desde el punto de vista de la limpieza.

Ahora, específicamente con respecto a sus preguntas numeradas:

Con respecto al n. ° 1: __cplusplus debe definirse dentro de los bloques extern "C" . Sin embargo, esto no importa, ya que los bloques deben anidar prolijamente.

En relación con el n. ° 2: __cplusplus se definirá para cualquier unidad de comstackción que se ejecute a través del comstackdor de C ++. En general, eso significa archivos .cpp y cualquier archivo incluido por ese archivo .cpp. El mismo .h (o .hh o .hpp o lo que-tiene-usted) podría interpretarse como C o C ++ en diferentes momentos, si las incluyen diferentes unidades de comstackción. Si desea que los prototipos en el archivo .h se refieran a los nombres de los símbolos C, entonces deben tener una extern "C" cuando se interpretan como C ++, y no deben tener una extern "C" cuando se interpretan como C, de ahí el #ifdef __cplusplus checking.

Para responder a su pregunta n. ° 3: las funciones sin prototipos tendrán enlaces C ++ si están en archivos .cpp y no dentro de un bloque extern "C" . Esto está bien, sin embargo, porque si no tiene un prototipo, solo puede ser llamado por otras funciones en el mismo archivo, y entonces generalmente no te importa cómo se ve el enlace, porque no estás planeando tener esa función. ser llamado por cualquier cosa fuera de la misma unidad de comstackción de todos modos.

Para el # 4, lo tienes exactamente. Si está incluyendo un encabezado para el código que tiene un enlace C (como el código comstackdo por un comstackdor de C), debe extern "C" al encabezado, de esa manera podrá vincularse con la biblioteca. (De lo contrario, su enlazador estaría buscando funciones con nombres como _Z1hic cuando buscaba void h(int, char)

5: Este tipo de mezcla es una razón común para usar la extern "C" , y no veo nada de malo en hacerlo de esta manera, solo asegúrate de entender lo que estás haciendo.

  1. extern "C" no cambia la presencia o ausencia de la macro __cplusplus . Simplemente cambia el enlace y el cambio de nombre de las declaraciones envueltas.

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

  3. Si comstack sus archivos .c como C ++, cualquier cosa que no esté en un bloque extern "C" y sin un prototipo extern "C" se tratará como una función C ++. Si los comstack como C, por supuesto, todo será una función C.

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

Un par de errores que son colaboraciones de la excelente respuesta de Andrew Shelansky y con los que discrepa un poco no cambian la forma en que el comstackdor lee el código

Debido a que los prototipos de su función se comstackn como C, no puede haber sobrecarga de los mismos nombres de funciones con diferentes parámetros: esa es una de las características clave del cambio de nombre del comstackdor. Se describe como un problema de vinculación, pero eso no es del todo cierto: recibirá errores tanto del comstackdor como del vinculador.

Los compiler errors serán si intenta utilizar las características de C ++ de la statement del prototipo, como la sobrecarga.

Los errores del enlazador ocurrirán más tarde porque su función parecerá no encontrada, si no tiene el contenedor externo “C” alrededor de las declaraciones y el encabezado está incluido en una mezcla de fuente C y C ++.

Una razón para disuadir a las personas de utilizar la comstackción C como configuración de C ++ es que esto significa que su código fuente ya no es portátil. Esa configuración es una configuración de proyecto, por lo que si un archivo .c se coloca en otro proyecto, no se comstackrá como c ++. Preferiría que la gente se tomara el tiempo para cambiar el nombre de los sufijos de archivo a .cpp.