Combiner C ++ et C - comment fonctionne #ifdef __cplusplus?

319

Je travaille sur un projet qui a beaucoup de code C hérité . Nous avons commencé à écrire en C ++, avec l'intention de convertir éventuellement le code hérité également. Je suis un peu confus quant à la façon dont le C et le C ++ interagissent. Je comprends qu'en enveloppant le code C avec extern "C"le compilateur C ++, les noms du code C ne seront pas modifiés , mais je ne sais pas exactement comment implémenter cela.

Donc, en haut de chaque fichier d'en-tête C (après les gardes d'inclusion), nous avons

#ifdef __cplusplus
extern "C" {
#endif

et en bas on écrit

#ifdef __cplusplus
}
#endif

Entre les deux, nous avons tous nos inclus, typedefs et prototypes de fonctions. J'ai quelques questions, pour voir si je comprends bien:

  1. Si j'ai un fichier C ++ A.hh qui comprend un fichier d'en-tête C Bh, comprend un autre fichier d'en-tête C Ch, comment cela fonctionne-t-il? Je pense que lorsque le compilateur __cplusplusentrera dans Bh, sera défini, donc il encapsulera le code avec extern "C" (et __cplusplusne sera pas défini à l'intérieur de ce bloc). Ainsi, quand il __cplusplusentrera dans Ch, il ne sera pas défini et le code ne sera pas encapsulé extern "C". Est-ce correct?

  2. Y a-t-il quelque chose de mal à envelopper un morceau de code avec extern "C" { extern "C" { .. } }? Que fera le second extern "C" ?

  3. Nous ne mettons pas ce wrapper autour des fichiers .c, seulement les fichiers .h. Alors, que se passe-t-il si une fonction n'a pas de prototype? Le compilateur pense-t-il que c'est une fonction C ++?

  4. Nous utilisons également du code tiers qui est écrit en C , et n'a pas ce genre d'enveloppe autour. Chaque fois que j'inclus un en-tête de cette bibliothèque, j'ai mis un extern "C"autour de #include. Est-ce la bonne façon de gérer cela?

  5. Enfin, cette configuration est-elle une bonne idée? Y a-t-il autre chose que nous devrions faire? Nous allons mélanger C et C ++ dans un avenir prévisible, et je veux m'assurer que nous couvrons toutes nos bases.

dublev
la source
2
En résumé, c'est la meilleure explication: To ensure that the names declared in that portion of code have C linkage, and thus C++ name mangling is not performed. (je l'ai obtenue du lien )
anhldbk
Vous n'avez pas à mettre le nom du langage C en gras
Edward Karak

Réponses:

290

extern "C"ne change pas vraiment la façon dont le compilateur lit le code. Si votre code est dans un fichier .c, il sera compilé en C, s'il est dans un fichier .cpp, il sera compilé en C ++ (sauf si vous faites quelque chose d'étrange à votre configuration).

Ce extern "C"qui affecte le lien. Les fonctions C ++, une fois compilées, voient leur nom modifié - c'est ce qui rend possible la surcharge. Le nom de la fonction est modifié en fonction des types et du nombre de paramètres, de sorte que deux fonctions portant le même nom auront des noms de symboles différents.

Le code à l'intérieur d'un extern "C"est toujours du code C ++. Il y a des limites à ce que vous pouvez faire dans un bloc "C" externe, mais elles concernent toutes la liaison. Vous ne pouvez pas définir de nouveaux symboles qui ne peuvent pas être créés avec la liaison C. Cela signifie pas de classes ou de modèles, par exemple.

extern "C"les blocs se nichent bien. Il y a aussi extern "C++"si vous vous retrouvez désespérément piégé à l'intérieur des extern "C"régions, mais ce n'est pas une si bonne idée du point de vue de la propreté.

Maintenant, concernant spécifiquement vos questions numérotées:

Concernant # 1: __cplusplus restera défini à l'intérieur des extern "C"blocs. Cela n'a pas d'importance, cependant, car les blocs doivent s'imbriquer parfaitement.

Concernant # 2: __cplusplus sera défini pour toute unité de compilation exécutée via le compilateur C ++. En règle générale, cela signifie les fichiers .cpp et tous les fichiers inclus par ce fichier .cpp. Le même .h (ou .hh ou .hpp ou ce que vous avez) pourrait être interprété comme C ou C ++ à différents moments, si différentes unités de compilation les incluent. Si vous voulez que les prototypes du fichier .h fassent référence aux noms de symboles C, ils doivent en avoir extern "C"lorsqu'ils sont interprétés en C ++, et ils ne devraient pas en avoir extern "C"lorsqu'ils sont interprétés en C - d'où la #ifdef __cplusplusvérification.

Pour répondre à votre question n ° 3: les fonctions sans prototypes auront une liaison C ++ si elles sont dans des fichiers .cpp et non à l'intérieur d'un extern "C"bloc. C'est bien, cependant, car s'il n'a pas de prototype, il ne peut être appelé que par d'autres fonctions dans le même fichier, et vous ne vous souciez généralement pas de l'apparence de la liaison, car vous ne prévoyez pas d'avoir cette fonction être appelé de toute façon en dehors de la même unité de compilation.

Pour # 4, vous l'avez exactement. Si vous incluez un en-tête pour le code qui a une liaison C (tel que le code qui a été compilé par un compilateur C), alors vous devez extern "C"l'en-tête - de cette façon, vous pourrez vous lier à la bibliothèque. (Sinon, votre éditeur de liens chercherait des fonctions avec des noms comme _Z1hiclorsque vous cherchiezvoid h(int, char)

5: Ce type de mélange est une raison courante à utiliser extern "C", et je ne vois rien de mal à le faire de cette façon - assurez-vous simplement de comprendre ce que vous faites.

Andrew Shelansky
la source
10
Bon pour mentionner extern "C++"quand votre en-tête / code C ++ est piégé profondément dans du code C
deddebme
1
J'ai écrit un programme C simple. À l'intérieur, j'ai ajouté le bloc #ifdef __cplusplus et ajouté un printf ("__ cplusplus defined \ n"); en elle. Si je le compile avec gcc, "__cplusplus defined" n'est pas imprimé, mais si je le compile avec g ++, il est imprimé. Donc je pense que __cplusplus signifie que le compilateur est un compilateur C ++ (vous l'avez dit). N'est-ce pas correct? (parce que je vous ai vu dire '__cplusplus devrait être défini à l'intérieur des blocs externes "C"'. pouvons-nous définir explicitement __cplusplus?
Chan Kim
1
Alors que vous devriez être en mesure de définir (presque) tout ce que vous voulez, le but __cplusplusest de déterminer si C++est utilisé vs C, donc le définir manuellement / explicitement défie le but de celui-ci ...
nurchi
Le "C" externe n'est en effet pas sur la façon dont le compilateur voit le fichier source, il est tout sur la façon dont il voit le fichier d'en-tête. Les structures peuvent avoir des tailles différentes lorsqu'elles sont compilées en C vs C ++, il y a le changement de nom bien sûr, et potentiellement d'autres différences aussi.
Nick
39
  1. extern "C"ne modifie pas la présence ou l'absence de la __cplusplusmacro. Il modifie simplement le lien et le changement de nom des déclarations encapsulées.

  2. Vous pouvez emboîter des extern "C"blocs très heureusement.

  3. Si vous compilez vos .cfichiers en C ++, tout ce qui n'est pas dans un extern "C"bloc et sans extern "C"prototype sera traité comme une fonction C ++. Si vous les compilez en C, alors bien sûr, tout sera une fonction C.

  4. Oui

  5. Vous pouvez mélanger C et C ++ en toute sécurité de cette manière.

Anthony Williams
la source
Si vous compilez des .cfichiers en C ++, tout est compilé en code C ++, même s'il est dans un extern "C"bloc. Le extern "C"code ne peut pas utiliser des fonctionnalités qui dépendent des conventions d'appel C ++ (par exemple la surcharge de l'opérateur) mais le corps de la fonction est toujours compilé en C ++, avec tout ce que cela implique.
David C.
21

Un couple de pièges qui sont des complices de l'excellente réponse d'Andrew Shelansky et en désaccord un peu avec ne change pas vraiment la façon dont le compilateur lit le code

Parce que vos prototypes de fonctions sont compilés en C, vous ne pouvez pas avoir une surcharge des mêmes noms de fonctions avec des paramètres différents - c'est l'une des principales caractéristiques du changement de nom du compilateur. Il est décrit comme un problème de liaison, mais ce n'est pas tout à fait vrai - vous obtiendrez des erreurs à la fois du compilateur et de l'éditeur de liens.

Les erreurs du compilateur seront si vous essayez d'utiliser les fonctionnalités C ++ de la déclaration de prototype telles que la surcharge.

Les erreurs de l'éditeur de liens se produiront plus tard, car votre fonction semblera introuvable, si vous n'avez pas le wrapper externe "C" autour des déclarations et que l'en-tête est inclus dans un mélange de sources C et C ++.

L'une des raisons pour dissuader les gens d'utiliser le compilé C en tant que paramètre C ++ est que cela signifie que leur code source n'est plus portable. Ce paramètre est un paramètre de projet et donc si un fichier .c est déposé dans un autre projet, il ne sera pas compilé en c ++. Je préfère que les gens prennent le temps de renommer les suffixes de fichiers en .cpp.

Andy Dent
la source
1
C'était la cause cryptique, arrachant mes cheveux. Doit vraiment être affiché quelque part.
Mitchell Currie
3

Il s'agit de l'ABI, afin de permettre aux applications C et C ++ d'utiliser les interfaces C sans aucun problème.

Étant donné que le langage C est très facile, la génération de code a été stable pendant de nombreuses années pour différents compilateurs, tels que GCC, Borland C \ C ++, MSVC, etc.

Alors que C ++ devient de plus en plus populaire, beaucoup de choses doivent être ajoutées dans le nouveau domaine C ++ (par exemple, finalement, le Cfront a été abandonné chez AT&T parce que C ne pouvait pas couvrir toutes les fonctionnalités dont il avait besoin). Telles que la fonctionnalité de modèle et la génération de code au moment de la compilation, par le passé, les différents fournisseurs de compilateurs ont effectivement mis en œuvre séparément le compilateur et l'éditeur de liens C ++, les ABI réels ne sont pas du tout compatibles avec le programme C ++ sur différentes plates-formes.

Les gens pourraient toujours aimer implémenter le programme réel en C ++ mais garder toujours l'ancienne interface C et ABI comme d'habitude, le fichier d'en-tête doit déclarer extern "C" {} , il indique au compilateur de générer un C ABI compatible / ancien / simple / facile pour les fonctions d'interface si le compilateur est un compilateur C et non un compilateur C ++.

Bo Zhou
la source