web-dev-qa-db-fra.com

Aucun avertissement avec la chaîne C non initialisée

Je me demande actuellement pourquoi je n'obtiens pas d'erreur de GCC lors de la compilation/liaison d'un petit programme C.

J'ai déclaré en version.h la chaîne suivante:

const char* const VERSION;

Dans version.c J'ai défini l'initialisation de la variable:

const char* const VERSION = "0.8 rev 213";

Aucun problème avec cela. Je peux utiliser la chaîne dans le reste du programme.

Si le fichier c est manquant, aucune erreur ne se produit lors de la compilation/liaison mais le programme échoue avec SIGSEGV (bien sûr) lorsqu'il essaie d'accéder à la variable.

Ma façon de configurer la variable VERSION est-elle correcte ou existe-t-il une meilleure méthode? Ou est-il possible d'obtenir une erreur lors de la compilation/liaison?

24
Max Senft

Vous avez défini (pas seulement déclaré) une variable dans l'en-tête.

Si vous incluez cet en-tête à partir de plusieurs fichiers source, le comportement est non défini . Voici la citation pertinente de la norme:

J.2 Comportement non défini

Un identifiant avec liaison externe est utilisé, mais dans le programme, il n'existe pas exactement une définition externe pour l'identifiant, ou l'identifiant n'est pas utilisé et il existe plusieurs définitions externes pour l'identifiant.

Vous vous basez ici sur un comportement spécifique à GCC (en fait commun à de nombreux compilateurs, mais toujours non standard), qui fusionne des définitions de données dupliquées provisoire. Voir l'aide pour -fcommon et -fno-common Indicateurs de compilation GCC. Tous les compilateurs ne se comportent pas de cette façon. Historiquement, c'est le comportement commun des éditeurs de liens, car c'est ainsi que fonctionnait Fortran avant le C.

En supposant cette extension de langage, l'une des définitions (celle qui a un initialiseur explicite) initialise la variable pour pointer vers votre littéral de chaîne. Mais si vous omettez cette définition, elle restera initialisée à zéro. Autrement dit, ce sera un pointeur nul constant. Pas très utile.

Pour faire court, ne faites jamais ça. Pour déclarer (mais pas définir) une variable globale dans un en-tête, utilisez extern. Si vous le faites et essayez d'omettre une définition ailleurs, vous obtiendrez probablement une erreur de l'éditeur de liens (bien que la norme ne nécessite pas de diagnostic pour cette violation, toutes les implémentations connues en produisent une).

31
n.m.

Votre exemple fonctionne en raison d'une caractéristique (mis) d'inspiration Fortran de C (mais pas C++) appelée définitions provisoires ( 6.9.2p2 ) qui est couramment mais non standard étendu à plusieurs fichiers.

Les définitions provisoires sont des déclarations de variables sans extern et sans initialiseur. Dans les implémentations courantes (jeu de mots), les définitions provisoires créent un type spécial de symbole qui est appelé symbole common. Lors de la liaison, en présence d'un symbole régulier du même nom, d'autres symboles communs deviennent des références au symbole régulier, ce qui signifie que tous les VERSIONs vides dans vos unités de traduction qui y sont créées en raison d'inclusions deviendront des références au régulier symbole const char* const VERSION = "0.8 rev 213";. S'il n'y a pas un tel symbole régulier, les symboles communs seront fusionnés en une seule variable zéro initalisée.

Je ne sais pas comment obtenir un avertissement contre cela avec un compilateur C.

Cette

const char* const VERSION;
const char* const VERSION = "0.8 rev 213";

semble fonctionner avec gcc, peu importe ce que j'ai essayé (g++ ne l'acceptera pas - C++ n'a pas la fonction de définition provisoire et il n'aime pas les variables const qui ne sont pas explicitement initialisées). Mais vous pouvez compiler avec -fno-common (qui est une option non standard assez "courante" (et hautement recommandée) (gcc, clang et tcc l'ont tous)) et vous obtiendrez alors une erreur de l'éditeur de liens si les déclarations non initialisées et initialisées sans extern sont dans différentes unités de traduction.

Exemple:

v.c:

const char * VERSION;

principal c

const char* VERSION;
int main(){}

compilation et liaison:

gcc main.c v.c #no error because of tentative definitions
g++ main.c v.c #linker error because C++ doesn't have tentative definitions
gcc main.c v.c -fno-common #linker error because tentative defs. are disabled

(J'ai supprimé le second const dans l'exemple pour le bien de l'exemple C++ - C++ rend également const globals statiques ou quelque chose comme ça qui ne fait que compliquer la démonstration de la fonction de définition provisoire.)

Lorsque les définitions provisoires sont désactivées ou avec C++, toutes vos déclarations de variables dans les en-têtes doivent contenir extern

version.h:

extern const char* const VERSION;

et vous devez avoir exactement une définition pour chaque global et cette définition doit avoir un initialiseur ou être sans extern (certains compilateurs avertiront si vous appliquez extern à une variable initialisée).

version.c:

#include "version.h" //optional; for type checking
const char* const VERSION = "0.8 rev 213";
15
PSkocik

Dans version.h vous devez déclarer VERSION comme extern comme

extern const char* const VERSION;

Et en version.c vous devez définir la variable externe comme

const char* const VERSION = "0.8 rev 213";

EDIT: - Vous devez également vous assurer qu'un seul fichier source a défini la variable VERSION tandis que les autres fichiers source l'ont déclarée extern et vous pouvez le faire en définissant dans un fichier source VERSION.c et placez la déclaration extern dans un fichier d'en-tête VERSION.h.

6
Achal