-3
votes

C VS C ++ Variable globale dans l'en-tête

Je sais que les variables globales ne doivent pas être définies dans un en-tête et que nous devrions plutôt utiliser extern pour ne les déclarer que dans l'en-tête.

Mais j'ai néanmoins essayé de définir une variable globale dans l'en-tête suivant lib.h : xxx

J'ai des résultats intéressants lorsque vous essayez d'utiliser cet en-tête en C et en C ++

in c, j'ai inclus l'en-tête dans main.c et dans lib.c , et il compile et fonctionne simplement bien: xxx

mais quand je l'exécute en C ++ avec un code similaire ( lib.h et lib.cpp est identique que ci-dessus), il donne un message d'erreur sur la variable i ayant plusieurs définitions: xxx < p> Pourquoi compile-t-il en C et non en C ++?


17 commentaires

Commencez par éliminer en utilisant NAMESPACE STD; , c'est exactement le genre de chose qu'il peut causer.


@Tzaulmen j'ai essayé et ça ne marche pas.


Hmm, laissez-moi courir à travers mon IDE


Comment compilez-vous les deux programmes? Afficher la ligne de commande de compilation. En outre, la variable i est ininitialisée.


Ceci n'est pas valide dans les deux langues, mais au moins GCC (en mode C) le permet par défaut - Quel compilateur utilisez-vous?


@aschepler invalide? Pourquoi? C'est une mauvaise idée, mais valable ... Avez-vous voulu utiliser une variable non initialisée?


@ Eugenesh.Chis jette une erreur de compilateur dans MSVC14, comme pour OP.


@Eugenesh. Je ne sais pas sur C, mais en C ++, il viole la règle d'une définition.


@Eugenesh. Violation de l'ORD. En C, c'est une définition provisoire, mais a toujours un lien externe, et chaque TU agira comme s'il y a une définition avec l'initialisateur zéro. En C ++, il ne s'agit que de deux définitions, période.


Le code est un peu malade de mauvaises pratiques, je crois que je connais la réponse, cependant.


@schepler ahh. Je n'ai pas remarqué que nous avons deux TUS, y compris l'en-tête ...


L'en-tête est copié dans le fichier du CPP (c'est ce que #include). Donc, si vous l'incluez dans deux fichiers CPP, vous venez de définir deux globaux différents avec le même nom. C'est littéralement le message d'erreur que vous avez posté, alors ... c'est un bon message d'erreur, et pas du tout surprenant.


C ++ et C ont à la fois une règle d'une définition. La différence est que c dit que si un comportement violé n'est pas défini. Certains lieurs C permettent de relier des variables ininitialisées avec le même nom, voir Extensions communes C11 J.5, J.5.11 Plusieurs définitions externes . Cela ne fonctionne pas par exemple dans MSVC.


Strictement, il compile à la fois C et C ++ - l'erreur est une erreur de liaison plutôt qu'une erreur de compilateur.


@Tzalumen Cela n'a rien à voir avec à l'aide de NAMESPACE STD; .


@Clifford strictement la norme C n'utilise pas le verbe compile du tout, mais traduisez ; Le mot se compilet elle-même est sémantiquement ambigu; Il est généralement communiqué d'être un synonyme de traduire dans les environnements communs C. C ne nécessite même pas de lieur, on vient de dire que c'est une possibilité.


@Anttihaapala: D'accord, mais je parle de la mise en œuvre des termes et de la chaîne d'outils utilisés par l'OP dans cette question question, pas la norme C. La distinction a pratique pratique plutôt que académique utilitaire lors de la correction des problèmes ou de la compréhension des diagnostics.


4 Réponses :


0
votes

Je ne sais pas pourquoi cela fonctionne dans C mais c'est faux en C et C ++. Essayez: XXX PRE>

// lib.c or lib.cpp
#include "lib.h"
int i = 0;
void add()
{
  ++i;
}


4 commentaires

Si le compilateur est GCC, voir GCC.GNU .org / onlinedocs / GCC / ... . GCC obéit la standard C ici lors de l'utilisation -fno-courant , mais sa valeur par défaut est généralement -fcommon .


@Schepler Il n'y a rien à obéir, car la norme C dit simplement que "le comportement est indéfini "


Je pense que toute la question concerne la raison pour laquelle il compile en C mais pas en C ++ - pas comment le rendre compilé en C ++.


@Anttihaapala droite, bon point.



-2
votes

Alors, voici la chose délicate à propos du préprocesseur: il copie et pâtes lorsque vous utilisez un #define. Ce qui signifie que le int i; que main.cpp voit n'est pas le même int i; que lib.cpp voit.


0 commentaires

1
votes

Lorsque je l'exécute en C ++ avec un code analogue, il donne un message d'erreur sur la variable I ayant plusieurs définitions. Pourquoi est-ce?

La norme C ++ dit:

[basic.def.odr] Chaque programme doit contenir exactement une définition de toutes les fonctions non lignes ou variable odr-utilisée dans ce programme en dehors d'un déclaration abandonnée; Aucun diagnostic requis.

lib.cpp (je suppose que votre fichier source "analogue" en C ++) et MAIN.CPP Définissez la variable globale int i . En tant que tel, le programme est mal formé.

solution: Ne déclarez que la variable dans l'en-tête. Définissez dans exactement une unité de traduction: xxx


2 commentaires

OP demande pourquoi il compile dans C.


@LightnessRacesinorbit N'hésitez pas à répondre à cela. Ils ont également demandé pourquoi il ne compile pas en C ++.



2
votes

Cette différence de comportement n'est pas une coïncidence, ni un bug dans les compilateurs. C'est l'application stricte des normes C et C ++, qui divergent sur la signification d'avoir plusieurs int i; dans la portée globale.

en C ++, il est invalide

in c ++, int i; est une définition d'un objet (non initialisé). Une règle de définition (ODR) ne vous permet pas de définir Plusieurs fois, la même variable globale.

Ceci est défini dans la norme C ++, dans la section [basic.def.odr]

en C Il est valide

in c, int i; est une définition provisoire . Il est parfaitement valable d'avoir plusieurs déclarations provisoires de la même variable globale.

Ceci est défini dans la norme C11, section 6.9.2 Définitions d'objet externe :

/ 2: une déclaration d'un identifiant pour un objet qui a une portée de fichier sans initialisateur, et sans spécificateur de classe de stockage ni avec Le spécificateur de classe de stockage statique constitue une provisoire définition. Si une unité de traduction contient un ou plusieurs provisoires Les définitions d'un identifiant et de l'unité de traduction ne contiennent pas de Définition externe pour cet identifiant, alors le comportement est exactement comme si l'unité de traduction contient une déclaration de portée de la portée de cette Identifiant, avec le type composite à partir de la fin de la traduction unité, avec une initialisée égale à 0.

Notez que cette clause est libellée d'une manière qui ne dit rien sur le cas où la même variable est définie dans plusieurs unités de traduction. La dernière phrase de la citation standard ci-dessus ne signifie pas que c'est une variable différente dans chaque fichier (pour cela, vous auriez besoin de liaison interne, avec statique ). Il dit simplement que le comportement est comme si la valeur initiale de la variable serait de 0.

Cette neutralité a une raison:

  • La norme identifie le cas comme comportement endédifié:

    Annexe J.2: Un identifiant avec une liaison externe est utilisé, mais dans le programme là-bas n'existe pas exactement une définition externe pour l'identifiant, ou L'identifiant n'est pas utilisé et il existe plusieurs externes Définitions pour l'identifiant

  • Mais la norme identifie également le cas avec plusieurs définitions comme une extension commune largement prise en charge, tant que ces définitions ne se contredisent pas les unes des autres:

    Annexe J.5.11: Il peut y avoir plus d'une définition externe pour l'identifiant d'un objet, avec ou sans l'utilisation explicite de la Mot-clé extern ; Si les définitions sont en désaccord, ou plus d'un est initialisé, le comportement est indéfini

    conseil important

    Pour cette raison, si vous souhaitez écrire un code portable, je recommande fortement d'utiliser extern dans l'en-tête et de définir la valeur dans une seule et unique de l'unité de compilation. Ceci est sûr, clair, sans ambiguïté et fonctionne en C ainsi que C ++.


8 commentaires

Cela dispose de 2 définitions de 2 fichiers différents qui ont un comportement non défini en C mais fonctionne comme une extension commune. Non plus int i; ont une liaison interne.


En effet, ce n'est pas un lien interne, ma faute. J'ai modifié et ajouté la citation qui montre qu'il est parfaitement valide pour avoir plusieurs définitions provisoires dans plusieurs unités de traduction.


@LighessRacsinorbit C'est la seule réponse qui parle de C, mais elle est incorrecte dans cela. Voir C11 Annexe J.2. quelle liste: "Un identifiant avec une liaison externe est utilisé, mais dans le programme, il n'existe pas exactement une définition externe pour l'identifiant, [...] (6.9)."


@Christophe que le libellé standard parle de multiples déclarations provisoires au sein du même TU, non de TUS.


@LighessRacsinorbit En effet, la façon dont il est libellé ne dit rien sur plusieurs fichiers. Et Antidi a raison à propos de J.2. Mais J.5.11 précise que c'est une extension commune tant que les différentes définitions ne se contredisent pas (qui était ma compréhension et aussi mon expérience). Je modifierai pour terminer ces différents aspects.


@Anttihaapala OK! Vous avez raison sur J.2! Mais il y a aussi J5.11 qui reconnaît qu'il s'agit d'une extension largement prise en charge (bien que non portable). Donc j'ai édité pour éviter de fausses impressions ;-)


Le fait est que lorsque vous regardez dans C, le comportement de cela est indéfini. Il n'est en aucun cas distinct de dire le pointeur NULL DÉRÉFERENDING ou divisant par zéro, ou Écrire aux littéraux strings . Vous discutez que cela va bien, juste parce qu'il est répertorié dans les extensions communes.


"Notez que cette clause est libellée d'une manière qui ne dit rien sur le cas où la même variable est définie dans plusieurs unités de traduction" droite, car il ne s'agit pas de cette affaire, et donc non plus t lié à l'affaire dans la question.