web-dev-qa-db-fra.com

Définitions de structures et de fonctions dans la portée

Donc, pour autant que je sache, c'est légal en C:

foo.c

struct foo {
   int a;
};

bar.c

struct foo {
    char a;
};

Mais la même chose avec les fonctions est illégale:

foo.c

int foo() {
    return 1;
}

bar.c

int foo() {
    return 0;
}

et entraînera une erreur de liaison (définition multiple de la fonction foo).

Pourquoi donc? Quelle est la différence entre les noms de structure et les noms de fonction qui empêche C de gérer l'un mais pas l'autre? Ce comportement s'étend-il également au C++?

28
V0ldek

Pourquoi donc?

struct foo {
   int a;
};

définit un modèle de création d'objets. Il ne crée aucun objet ni fonction. Sauf si struct foo est utilisé quelque part dans votre code, en ce qui concerne le compilateur/éditeur de liens, ces lignes de code peuvent tout aussi bien ne pas exister.

Veuillez noter qu'il existe une différence dans la façon dont C et C++ gèrent les définitions struct incompatibles.

Les différentes définitions de struct foo dans votre code affiché, est correct dans un programme C tant que vous ne mélangez pas leur utilisation.

Cependant, ce n'est pas légal en C++. En C++, ils ont un lien externe et doivent être définis de manière identique. Voir .2 Une règle de définition/5 pour plus de détails.

24
R Sahu

Le concept distinctif dans ce cas est appelé linkage.

En C struct, les balises union ou enum ont pas de lien. Ils sont effectivement locaux à leur portée.

6.2.2 Liens d'identifiants
6 Les identifiants suivants n'ont pas de lien: un identifiant déclaré autre chose qu'un objet ou une fonction; un identifiant déclaré comme paramètre de fonction; un identificateur de portée de bloc pour un objet déclaré sans le spécificateur de classe de stockage extern.

Ils ne peuvent pas être re-déclarés dans la même portée (sauf pour les soi-disant déclarations en avant). Mais ils peuvent être librement re-déclarés dans différentes portées, y compris différentes unités de traduction. Dans différentes étendues, ils peuvent déclarer des types complètement indépendants. Voici ce que vous avez dans votre exemple: dans deux unités de traduction différentes (c'est-à-dire dans deux portées de fichiers différentes), vous avez déclaré deux _ différentes et non liées struct foo les types. C'est parfaitement légal.

Pendant ce temps, les fonctions ont un lien en C. Dans votre exemple, ces deux définitions définissent la même fonction foo avec external lien. Et vous n'êtes pas autorisé à fournir plus d'une définition d'une fonction de liaison externe dans l'ensemble de votre programme

6.9 Définitions externes
5 [...] Si un identifiant déclaré avec liaison externe est utilisé dans une expression (autre que dans le cadre de l'opérande de un sizeof ou _Alignof opérateur dont le résultat est une constante entière), quelque part dans l'ensemble du programme, il doit y avoir exactement une définition externe pour l'identifiant; sinon, il n'y en aura pas plus d'un.


En C++, le concept de linkage est étendu: il attribue un lien spécifique à une plus grande variété d'entités, y compris des types. En classe C++, les types ont une liaison. Les classes déclarées dans la portée de l'espace de noms ont liaison externe. Et une règle de définition de C++ stipule explicitement que si une classe avec liaison externe a plusieurs définitions (sur différentes unités de traduction), elle doit être définie de manière équivalente dans toutes ces unités de traduction ( http://eel.is/c+ + draft/basic.def.odr # 12 ). Donc, en C++, vos définitions struct seraient illégales.

Vos définitions de fonction restent également illégales en C++ en raison de la règle C++ ODR (mais essentiellement pour les mêmes raisons qu'en C).

18
AnT

Vos définitions de fonction déclarent toutes deux une entité appelée foo avec liaison externe, et la norme C indique qu'il ne doit pas y avoir plus d'une définition d'une entité avec liaison externe. Les types de structure que vous avez définis ne sont pas des entités avec une liaison externe, vous pouvez donc avoir plusieurs définitions de struct foo.

Si vous avez déclaré des objets avec une liaison externe en utilisant le même nom, ce serait une erreur:

foo.c

struct foo {
   int a;
};
struct foo obj;

bar.c

struct foo {
    char a;
};
struct foo obj;

Maintenant, vous avez deux objets appelés obj qui ont tous deux une liaison externe, ce qui n'est pas autorisé.

Ce serait toujours faux même si l'un des objets est seulement déclaré, non défini:

foo.c

struct foo {
   int a;
};
struct foo obj;

bar.c

struct foo {
    char a;
};
extern struct foo obj;

Ceci n'est pas défini, car les deux déclarations de obj font référence au même objet, mais elles n'ont pas de types compatibles (car struct foo est défini différemment dans chaque fichier).

C++ a des règles similaires, mais plus complexes, pour tenir compte des fonctions inline et inline des variables, des modèles et d'autres fonctionnalités C++. En C++, les exigences pertinentes sont connues sous le nom de règle à définition unique (ou ODR). Une différence notable est que C++ n'autorise même pas les deux définitions struct différentes, même si elles ne sont jamais utilisées pour déclarer des objets avec une liaison externe ou autrement "partagés" entre les unités de traduction.

12
Jonathan Wakely

Les deux déclarations pour struct foo sont incompatibles car les types de membres ne sont pas les mêmes. Les utiliser tous les deux dans chaque unité de traduction est très bien tant que vous ne faites rien pour confondre les deux.

Si par exemple vous avez fait ceci:

foo.c:

struct foo {
   char a;
};

void bar_func(struct foo *f);

void foo_func()
{
    struct foo f;
    bar_func(&f);
}

bar.c:

struct foo {
   int a;
};

void bar_func(struct foo *f)
{
    f.a = 1000;
}

Vous invoqueriez comportement indéfini car le struct foo cette bar_func attend n'est pas compatible avec le struct foo cette foo_func fournit.

La compatibilité des structures est détaillée dans la section 6.2.7 de la norme C :

1 Deux types ont un type compatible si leurs types sont identiques. Des règles supplémentaires permettant de déterminer si deux types sont compatibles sont décrites au 6.7.2 pour les spécificateurs de type, au 6.7.3 pour les qualificateurs de type et au 6.7.6 pour les déclarants. De plus, deux types de structure, d'union ou énumérés déclarés dans des unités de traduction distinctes sont compatibles si leurs balises et membres satisfont aux exigences suivantes: Si l'un est déclaré avec une balise, l'autre doit être déclaré avec la même balise. Si les deux sont complétés n'importe où dans leurs unités de traduction respectives, les exigences supplémentaires suivantes s'appliquent: il doit y avoir une correspondance biunivoque entre leurs membres de sorte que chaque paire de membres correspondants soit déclarée avec des types compatibles; si un membre de la paire est déclaré avec un spécificateur d'alignement, l'autre est déclaré avec un spécificateur d'alignement équivalent; et si un membre de la paire est déclaré avec un nom, l'autre est déclaré avec le même nom. Pour deux structures, les membres correspondants doivent être déclarés dans le même ordre. Pour deux structures ou unions, les champs binaires correspondants doivent avoir les mêmes largeurs. Pour deux énumérations, les membres correspondants doivent avoir les mêmes valeurs.

2 Toutes les déclarations qui se réfèrent au même objet ou fonction doivent avoir un type compatible; sinon, le comportement n'est pas défini.

Pour résumer, les deux instances de struct foo doit avoir des membres avec le même nom et le même type et dans le même ordre pour être compatible.

Ces règles sont nécessaires pour qu'un struct puisse être défini une fois dans un fichier d'en-tête et que cet en-tête soit ensuite inclus dans plusieurs fichiers source. Il en résulte que struct est défini dans plusieurs fichiers source, mais que chaque instance est compatible.

3
dbush

Pour masquer la définition de fonction de l'éditeur de liens, utilisez le mot-clé statique.

foo.c

    static int foo() {
        return 1;
    }

bar.c

    static int foo() {
        return 0;
    }
2
armagedescu

La différence n'est pas tant dans les noms que dans l'existence; une définition de structure n'est stockée nulle part et son nom n'existe que lors de la compilation.
.

D'un autre côté, une fonction doit être stockée quelque part, et si elle a un lien externe, l'éditeur de liens a besoin de son nom.

Si vous faites vos fonctions static, afin qu'elles soient "invisibles" en dehors de leur unité de compilation respective, l'erreur de liaison disparaîtra.

2
molbdnilo