web-dev-qa-db-fra.com

Combinaison de C++ et de C - Comment fonctionne #ifdef __cplusplus?

Je travaille sur un projet qui contient beaucoup de code hérité C. Nous avons commencé à écrire en C++, dans le but de convertir éventuellement le code hérité. Je suis un peu confus quant à la façon dont C et C++ interagissent. Je comprends qu'en encapsulant le code C avec extern "C", le compilateur C++ ne modifie pas les noms de code C, mais je ne suis pas tout à fait sûr de la mettre en œuvre.

Ainsi, 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, nous écrivons

#ifdef __cplusplus
}
#endif

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

  1. Si j'ai un fichier C++ A.hh qui Inclut un fichier d'en-tête C, B.h, .__ inclut un autre fichier d'en-tête C C.h, Comment cela fonctionne-t-il? Je pense que Lorsque le compilateur entrera dans B.h, le __cplusplus sera défini, de sorte qu'il ajoutera le code extern "C".__ (et __cplusplus ne sera pas défini à l'intérieur de ce bloc). Ainsi, Lorsqu'il entrera dans C.h, __cplusplus ne sera pas défini Et le code ne sera pas encapsulé dans extern "C". Est-ce correct?

  2. Est-ce qu'il y a un problème avec Envelopper un morceau de code avec extern "C" { extern "C" { .. } }? Quel sera le deuxième extern "C"do?

  3. Nous ne mettons pas ce wrapper autour des fichiers .c, mais uniquement des 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 un code tiers Qui est écrit en C, et ne dispose pas de ce type d’emballageit. Chaque fois que j'inclue un en-tête De cette bibliothèque, je metsanextern "C" autour de #include . Est-ce la bonne façon de traiter avec

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

274
dublev

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++ (à moins que vous ne fassiez quelque chose d'étrange avec votre configuration).

Ce que extern "C" affecte le couplage. Les fonctions C++, une fois compilées, voient leurs noms mutilés - c’est ce qui rend la surcharge possible. 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 symbole différents.

Le code dans un extern "C" est toujours du code C++. Il existe des limites à ce que vous pouvez faire dans un bloc "C" externe, mais elles sont toutes liées au couplage. Vous ne pouvez pas définir de nouveaux symboles qui ne peuvent pas être construits avec une liaison en C. Cela signifie pas de classes ou de modèles, par exemple.

extern "C" bloque bien les nids. Il y a aussi extern "C++" si vous êtes pris au piège dans les régions extern "C", mais ce n'est pas une si bonne idée du point de vue de la propreté.

Maintenant, en particulier en ce qui concerne vos questions numérotées:

Concernant # 1: __cplusplus devrait être défini à l'intérieur des blocs extern "C". Cela n'a pas d'importance, car les blocs doivent s'emboîter proprement.

En ce qui concerne le point 2: __cplusplus sera défini pour toute unité de compilation exécutée via le compilateur C++. Généralement, cela signifie que les fichiers .cpp et tous les fichiers inclus par ce fichier .cpp. Le même .h (ou .hh ou .hpp ou what-have-you) peut être interprété comme C ou C++ à des moments différents, si différentes unités de compilation les incluent. Si vous voulez que les prototypes du fichier .h fassent référence à des noms de symboles C, ils doivent alors avoir extern "C" lorsqu’ils sont interprétés en tant que C++ et ils ne doivent pas avoir extern "C" lorsqu’ils sont interprétés en C - d’où la vérification #ifdef __cplusplus.

Pour répondre à votre question n ° 3: les fonctions sans prototypes auront une liaison C++ si elles se trouvent dans des fichiers .cpp et non dans un bloc extern "C". C’est bien, car, s’il n’a pas de prototype, il ne peut être appelé que par d’autres fonctions dans le même fichier. Dans ce cas, vous ne vous souciez généralement pas de la qualité du lien, car vous n’avez pas l’intention de le faire. être appelé par quoi que ce soit en dehors de la même unité de compilation de toute façon.

Pour le n ° 4, vous l'avez exactement. Si vous incluez un en-tête pour le code ayant une liaison C (par exemple, un code compilé par un compilateur C), vous devez alors extern "C" l'en-tête - vous pourrez ainsi créer un lien avec la bibliothèque. (Sinon, votre éditeur de liens rechercherait des fonctions portant des noms tels que _Z1hic lorsque vous rechercheriez void h(int, char)

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

239
Andrew Shelansky
  1. extern "C" ne modifie pas la présence ou l'absence de la macro __cplusplus. Cela ne fait que changer le lien et le changement de nom des déclarations encapsulées.

  2. Vous pouvez imbriquer des blocs extern "C" assez facilement.

  3. Si vous compilez vos fichiers .c en tant que C++, tout élément ne figurant pas dans un bloc extern "C" et sans prototype extern "C" sera traité comme une fonction C++. Si vous les compilez en C, tout sera évidemment une fonction C.

  4. Oui

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

36
Anthony Williams

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

Comme vos prototypes de fonctions sont compilés en C, vous ne pouvez pas surcharger les mêmes noms de fonctions avec des paramètres différents. C’est l’une des principales caractéristiques de la modification du nom du compilateur. C'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 se produiront 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 pas le wrapper extern "C" autour des déclarations et que l'en-tête est inclus dans un mélange de sources C et C++. .

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

19
Andy Dent

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

Comme le langage C est très simple, la génération de code est restée stable pendant de nombreuses années pour différents compilateurs, tels que GCC, Borland C\C++, MSVC, etc.

Alors que le C++ devient de plus en plus populaire, il faut ajouter beaucoup de choses dans le nouveau domaine C++ (par exemple, le Cfront a finalement été abandonné chez AT & T car le C ne pouvait pas couvrir toutes les fonctionnalités dont il avait besoin). Comme par exemple template feature et la génération de code à la compilation, par le passé, les différents éditeurs de compilateurs ont effectué l’implémentation réelle du compilateur C++ et de l’éditeur de liens, les ABI n’étant pas du tout compatibles avec le programme C++. plates-formes.

Les gens peuvent toujours aimer implémenter le programme réel en C++ tout en conservant l’ancienne interface C et ABI comme d'habitude, le fichier d'en-tête doit déclarer extern "C" {} , il indique au compilateur génère compatible/old/simple/easy ABI C pour les fonctions d'interface si le compilateur est un compilateur C et non pas un compilateur C++.

0
Bo Zhou