Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

#include globaux ou locaux a chaque fichier source?

11 réponses
Avatar
Vincent Lefevre
Bonjour,

J'aimerais savoir s'il y a une pratique recommandée de l'utilisation
des #include dans le cadre d'un logiciel avec plusieurs fichiers .c
(cas classique) et devant tourner sur de multiples plateformes (si
bien que tester la compilation sur une machine avant un commit ne
suffit pas toujours à détecter les erreurs). La solution actuellement
choisie dans notre logiciel: mettre les #include nécessaires au début
de chaque fichier .c, suivant les fonctions et macros utilisées par
le fichier .c en question.

Le problème qui s'est posé: j'ai déplacé une fonction d'un fichier .c
à un autre, mais j'ai oublié de mettre à jour les #include (je signale
qu'indépendamment d'autres choix qui ont pu être faits, ce genre
d'erreur ne se voit pas toujours à la compilation dans la pratique).
Comme chaque fichier .c inclut un fichier d'en-tête global au projet,
je pense à la solution alternative de mettre tous les #include dans ce
fichier d'en-tête. Qu'en pensez-vous?

D'autre part, certains en-têtes standard sont inclus conditionnellement
suivant la valeur de certaines macros (définies ou non en fonction des
options du "configure"), ce qui peut cacher des erreurs qui dépendent
du choix de ces options. Une inclusion conditionnelle est tout à fait
logique dans le cas d'un en-tête non standard (puisque justement,
l'en-tête peut ne pas exister sur certaines plateformes), mais dans
le cas d'un en-tête standard, cela me semble finalement une source
d'erreur. Vaut-il mieux éviter ces inclusions conditionnelles?

--
Vincent Lefèvre <vincent@vinc17.org> - Web: <http://www.vinc17.org/>
100% accessible validated (X)HTML - Blog: <http://www.vinc17.org/blog/>
Work: CR INRIA - computer arithmetic / Arenaire project (LIP, ENS-Lyon)

10 réponses

1 2
Avatar
Pascal Bourguignon
Vincent Lefevre <vincent+ writes:

Bonjour,

J'aimerais savoir s'il y a une pratique recommandée de l'utilisation
des #include dans le cadre d'un logiciel avec plusieurs fichiers .c
(cas classique) et devant tourner sur de multiples plateformes (si
bien que tester la compilation sur une machine avant un commit ne
suffit pas toujours à détecter les erreurs). La solution actuellement
choisie dans notre logiciel: mettre les #include nécessaires au début
de chaque fichier .c, suivant les fonctions et macros utilisées par
le fichier .c en question.

Le problème qui s'est posé: j'ai déplacé une fonction d'un fichier .c
à un autre, mais j'ai oublié de mettre à jour les #include (je signale
qu'indépendamment d'autres choix qui ont pu être faits, ce genre
d'erreur ne se voit pas toujours à la compilation dans la pratique).
Comme chaque fichier .c inclut un fichier d'en-tête global au projet,
je pense à la solution alternative de mettre tous les #include dans ce
fichier d'en-tête. Qu'en pensez-vous?

D'autre part, certains en-têtes standard sont inclus conditionnellement
suivant la valeur de certaines macros (définies ou non en fonction des
options du "configure"), ce qui peut cacher des erreurs qui dépendent
du choix de ces options. Une inclusion conditionnelle est tout à fait
logique dans le cas d'un en-tête non standard (puisque justement,
l'en-tête peut ne pas exister sur certaines plateformes), mais dans
le cas d'un en-tête standard, cela me semble finalement une source
d'erreur. Vaut-il mieux éviter ces inclusions conditionnelles?


Ça dépend de la taille du logiciel.

Si c'est un petit programme où ça ne dérange pas de recompiler tous
les sources à la moindre modification, on peut trés bien faire un
fichier tout.h et l'inclure partout.


Mais si le temps de compilation de l'ensemble est important, il vaut
mieux inclure les entêtes nécessaires de façon plus fine, afin de
permettre à make de ne sélectionner la recompilation que des fichiers
qui dépendent vraiment des interfaces modifiés.

À charge au programmeur de bien modifier les sources quand il déplace
des fonctions. man grep!


Pour ce qui est des dépendances systèmes, normalement ça devrait
passer par un module "système" qui devrait cacher le détail de ces
dépendances. Comme le système ne change pas en cours de route, on
peut sans problème inclure un seul entête pour tout le système.

--
__Pascal Bourguignon__ http://www.informatimago.com/

NOTE: The most fundamental particles in this product are held
together by a "gluing" force about which little is currently known
and whose adhesive power can therefore not be permanently
guaranteed.

Avatar
espie
In article <20070703225127$,
Vincent Lefevre <vincent+ wrote:
Bonjour,

J'aimerais savoir s'il y a une pratique recommandée de l'utilisation
des #include dans le cadre d'un logiciel avec plusieurs fichiers .c
(cas classique) et devant tourner sur de multiples plateformes (si
bien que tester la compilation sur une machine avant un commit ne
suffit pas toujours à détecter les erreurs). La solution actuellement
choisie dans notre logiciel: mettre les #include nécessaires au début
de chaque fichier .c, suivant les fonctions et macros utilisées par
le fichier .c en question.

Le problème qui s'est posé: j'ai déplacé une fonction d'un fichier .c
à un autre, mais j'ai oublié de mettre à jour les #include (je signale
qu'indépendamment d'autres choix qui ont pu être faits, ce genre
d'erreur ne se voit pas toujours à la compilation dans la pratique).
Comme chaque fichier .c inclut un fichier d'en-tête global au projet,
je pense à la solution alternative de mettre tous les #include dans ce
fichier d'en-tête. Qu'en pensez-vous?


Beaucoup de mal. Ca se termine vite en sac de noeud cote portabilite.
Si tu as un fichier d'entetes `fourre-tout', tu es plus ou moins oblige
de l'inclure dans tous les cas de figure. Si celui-ci entre en collision
avec des choses existant sur un systeme donne, ca devient complexe a
corriger, parce que c'est une grosse operation globale. Alors que faire
de la micro-chirurgie sur un fichier d'entete pas inclus par tout le monde,
c'est souvent plus facile.

Pour ce qui est de la mise-a-jour des includes, ca doit pas etre tres
complique de mettre en place un bout de script qui verifie que les choses
sont faites comme tu veux... je sais pas moi, un bete nm sur les fichiers
objets assorti d'un grep sur le fichier d'entete qui devrait contenir leur
prototype ?

D'autre part, certains en-têtes standard sont inclus conditionnellement
suivant la valeur de certaines macros (définies ou non en fonction des
options du "configure"), ce qui peut cacher des erreurs qui dépendent
du choix de ces options. Une inclusion conditionnelle est tout à fait
logique dans le cas d'un en-tête non standard (puisque justement,
l'en-tête peut ne pas exister sur certaines plateformes), mais dans
le cas d'un en-tête standard, cela me semble finalement une source
d'erreur. Vaut-il mieux éviter ces inclusions conditionnelles?


Ca depend de la facon dont l'inclusion conditionnelle est pratiquee, et ca
depend de ce qu'on va chercher dans l'entete standard. Si c'est systematique
et pour tous les entetes, c'est nocif. Si c'est pour des fonctionnalites C99,
ca peut valoir le coup, et ca peut sans doute se mettre out-of-line dans les
cas tordus.... je pense typiquement a stdint.h et cie.

Avatar
Laurent Deniau
Vincent Lefevre wrote:
Bonjour,

J'aimerais savoir s'il y a une pratique recommandée de l'utilisation
des #include dans le cadre d'un logiciel avec plusieurs fichiers .c
(cas classique) et devant tourner sur de multiples plateformes (si
bien que tester la compilation sur une machine avant un commit ne
suffit pas toujours à détecter les erreurs). La solution actuellement
choisie dans notre logiciel: mettre les #include nécessaires au début
de chaque fichier .c, suivant les fonctions et macros utilisées par
le fichier .c en question.

Le problème qui s'est posé: j'ai déplacé une fonction d'un fichier .c
à un autre, mais j'ai oublié de mettre à jour les #include (je signale
qu'indépendamment d'autres choix qui ont pu être faits, ce genre
d'erreur ne se voit pas toujours à la compilation dans la pratique).
Comme chaque fichier .c inclut un fichier d'en-tête global au projet,
je pense à la solution alternative de mettre tous les #include dans ce
fichier d'en-tête. Qu'en pensez-vous?


Si le projet utilise gcc,

-Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations

devraient eviter les problemes.

D'autre part, certains en-têtes standard sont inclus conditionnellement
suivant la valeur de certaines macros (définies ou non en fonction des
options du "configure"), ce qui peut cacher des erreurs qui dépendent
du choix de ces options. Une inclusion conditionnelle est tout à fait
logique dans le cas d'un en-tête non standard (puisque justement,
l'en-tête peut ne pas exister sur certaines plateformes), mais dans
le cas d'un en-tête standard, cela me semble finalement une source
d'erreur. Vaut-il mieux éviter ces inclusions conditionnelles?


Je suis pour la micro-chirurgie deja cite dans un autre post.

a+, ld.

Avatar
espie
In article <468b8c35$0$25944$,
Harpo wrote:
Contrairement à d'autres ici, je suis d'avis de mettre tout ce qui est
spécifique à la plateforme et aux options de générations dans un même
fichier, disons config.h et un
#include "config.h"
dans le fichier d'en-tête global inclu dans tous les .c


Je vais sortir le gros fusil de l'experience personnelle.

Je me suis deja battu maintes fois a vouloir porter des logiciels avec
des structures d'entetes imbriquees quelque peu complexes. C'est infiniment
plus chiant, lourdingue, et complexe a faire marcher que des situations ou
les entetes sont `a plat'... par exemple, c'est frequent d'avoir des problemes
d'ordre des entetes, ou des #define a mettre en place sur des fichiers precis
pour beneficier de certaines fonctions et definitions. Et bien, sur les
trucs qui s'incluent tous les uns les autres, tu te retrouves tres vite
a avoir des modifs en cascade, et a devoir demeler les entetes afin d'avoir
pile-poil les definitions dont tu as besoin et pas de conflit avec ton
logiciel.

Dans les exemples compliques, je citerais pas mal de bouts de logiciels GNU,
qui sont portables si on est, soit avec glibc, soit pas du tout de support
internationalisation, et qui s'accommodent tres mal de situations
intermediaires (avec, par exemple, des wchar_t, mais pas le gettext gnu).
Ces logiciels passent souvent par un ou deux fichiers centraux qui definissent
un peu tout (et surtout trop de choses), et qui contiennent, outre des
definitions fondamentales, des prototypes de fonction a peu pres systeme,
rearrangees a la sauce gnu (avec par exemple un const la ou il n'y en a pas
sur mon systeme). C'est generalement un bordel immonde a rearranger...

Avatar
Laurent Deniau
Harpo wrote:
Vincent Lefevre wrote:

Le problème qui s'est posé: j'ai déplacé une fonction d'un fichier .c
à un autre, mais j'ai oublié de mettre à jour les #include (je signale
qu'indépendamment d'autres choix qui ont pu être faits, ce genre
d'erreur ne se voit pas toujours à la compilation dans la pratique).
Comme chaque fichier .c inclut un fichier d'en-tête global au projet,
je pense à la solution alternative de mettre tous les #include dans ce
fichier d'en-tête. Qu'en pensez-vous?


Contrairement à d'autres ici, je suis d'avis de mettre tout ce qui est
spécifique à la plateforme et aux options de générations dans un même
fichier, disons config.h et un
#include "config.h"
dans le fichier d'en-tête global inclu dans tous les .c


Perso, je pense qu'il y a une difference entre la configuration et ce
qu'on en fait dans le code. A titre d'exemple, dans COS j'ai un fichier
config.h qui demande a l'installateur la configuration pour son systeme
et un setup.h qui complete certains points automatiquement (post
config). Ensuite j'ai des cos/thread.h ou cos/gc.h (prives) etc... qui
specialisent la config d'un domaine particulier.

http://cos.cvs.sourceforge.net/cos/COS/src/cos/cos/config.h?view=markup
http://cos.cvs.sourceforge.net/cos/COS/src/cos/cos/setup.h?view=markup

Ces fichiers sont un peu vieux mais je suis interesse par toutes
suggestions sur l'amelioration sur la config de COS.

a+, ld.


Avatar
Vincent Lefevre
Dans l'article ,
Pascal Bourguignon écrit:

Ça dépend de la taille du logiciel.

Si c'est un petit programme où ça ne dérange pas de recompiler tous
les sources à la moindre modification, on peut trés bien faire un
fichier tout.h et l'inclure partout.


Ce sont des en-têtes censés être mis une fois pour toute. Il y aurait
une recompilation juste pour la migration des en-têtes. Ce ne serait
pas un problème, même pour un gros logiciel.

À charge au programmeur de bien modifier les sources quand il déplace
des fonctions. man grep!


grep ne sert pas à grand chose ici, le C ne possédant pas de
namespace (surtout au niveau de la bibliothèque standard, qui
est le cas qui me préoccupe).

--
Vincent Lefèvre - Web: <http://www.vinc17.org/>
100% accessible validated (X)HTML - Blog: <http://www.vinc17.org/blog/>
Work: CR INRIA - computer arithmetic / Arenaire project (LIP, ENS-Lyon)

Avatar
Vincent Lefevre
Dans l'article <f6fidf$mt5$,
Marc Espie écrit:

Beaucoup de mal. Ca se termine vite en sac de noeud cote portabilite.
Si tu as un fichier d'entetes `fourre-tout', tu es plus ou moins oblige
de l'inclure dans tous les cas de figure. Si celui-ci entre en collision
avec des choses existant sur un systeme donne, ca devient complexe a
corriger, parce que c'est une grosse operation globale. Alors que faire
de la micro-chirurgie sur un fichier d'entete pas inclus par tout le monde,
c'est souvent plus facile.


Enfin, une bibliothèque ne devrait pas avoir de conflit avec les
en-têtes standard[*], sinon elle est bien buggée.

[*] Je pense essentiellement aux en-têtes standard, puisque ce sont
ceux qui posent le plus de problèmes en pratique: quand on utilise
certaines macros ou fonctions, on ne pense pas forcément à une
dépendance particulière (c'est le cas de NULL en particulier, qui
n'a a priori rien à voir avec une notion de bibliothèque).

Notre logiciel, c'est MPFR. Et il y a gmp.h à prendre en compte, car
on est complètement lié à GMP. Mais cet en-tête, on a justement décidé
de l'inclure en global.

Pour ce qui est de la mise-a-jour des includes, ca doit pas etre tres
complique de mettre en place un bout de script qui verifie que les choses
sont faites comme tu veux... je sais pas moi, un bete nm sur les fichiers
objets assorti d'un grep sur le fichier d'entete qui devrait contenir leur
prototype ?


Irréalisable en pratique (pense aux macros), surtout que le
préprocesseur peut intervenir.

Ca depend de la facon dont l'inclusion conditionnelle est pratiquee,
et ca depend de ce qu'on va chercher dans l'entete standard. Si
c'est systematique et pour tous les entetes, c'est nocif. Si c'est
pour des fonctionnalites C99, ca peut valoir le coup, et ca peut
sans doute se mettre out-of-line dans les cas tordus.... je pense
typiquement a stdint.h et cie.


Des en-têtes standard portables (<stdio.h>, <stdlib.h>, <limits.h>,
etc.).

--
Vincent Lefèvre - Web: <http://www.vinc17.org/>
100% accessible validated (X)HTML - Blog: <http://www.vinc17.org/blog/>
Work: CR INRIA - computer arithmetic / Arenaire project (LIP, ENS-Lyon)

Avatar
Vincent Lefevre
Dans l'article <f6fn8r$g3q$,
Laurent Deniau écrit:

Si le projet utilise gcc,

-Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations

devraient eviter les problemes.


Ce n'est pas suffisant à cause du préprocesseur, et en particulier
des en-têtes qui dépendent du système (certains incluent plus de
choses que ce qui est strictement nécessaire), i.e. on peut très
bien ne pas avoir de warning sur une plateforme, mais en avoir un
ailleurs.

--
Vincent Lefèvre - Web: <http://www.vinc17.org/>
100% accessible validated (X)HTML - Blog: <http://www.vinc17.org/blog/>
Work: CR INRIA - computer arithmetic / Arenaire project (LIP, ENS-Lyon)

Avatar
espie
In article <20070707101149$,
Vincent Lefevre <vincent+ wrote:
Dans l'article <f6fidf$mt5$,
Marc Espie écrit:

Beaucoup de mal. Ca se termine vite en sac de noeud cote portabilite.
Si tu as un fichier d'entetes `fourre-tout', tu es plus ou moins oblige
de l'inclure dans tous les cas de figure. Si celui-ci entre en collision
avec des choses existant sur un systeme donne, ca devient complexe a
corriger, parce que c'est une grosse operation globale. Alors que faire
de la micro-chirurgie sur un fichier d'entete pas inclus par tout le monde,
c'est souvent plus facile.


Enfin, une bibliothèque ne devrait pas avoir de conflit avec les
en-têtes standard[*], sinon elle est bien buggée.

[*] Je pense essentiellement aux en-têtes standard, puisque ce sont
ceux qui posent le plus de problèmes en pratique: quand on utilise
certaines macros ou fonctions, on ne pense pas forcément à une
dépendance particulière (c'est le cas de NULL en particulier, qui
n'a a priori rien à voir avec une notion de bibliothèque).


Je me doutais bien que c'etait de mpfr dont il etait question.
Deux contraintes a prendre en compte:
- les entetes standards ne le sont pas toujours autant qu'on le voudrait.
J'ai vu pas mal de systeme ou il faut rajouter des incantations comme
#define _XOPEN_SOURCE pour avoir certaines des fonctions que l'on voudrait,
et ou on se retrouve generalement avec des fonctions que l'on ne voudrait
pas... bonjour les collisions.
- on n'utilise rarement une bibliotheque toute seule... si elle envahit
plus que le strict minimum de l'espace de noms global, alors il est a peu
pres certain que tot ou tard, deux bibliotheques vont se marcher sur les
pieds... particulierement pour les typedef et les struct. La solution serait
peut-etre de tout prefixer d'un mpfr_ ou equivalent, mais c'est un peu
lourd.

Bref, il y a la theorie, et la pratique... dans la pratique, j'ai souvent
eu les merdes dont je te parle... et comme par hasard, c'est toujours
dans des situations tordues et fortement contraintes que ca se passe.

Plus les entetes sont faciles a demeler, plus ca simplifie la vie... Il n'y
a absolument aucun doute pour moi a ce niveau !


Avatar
Vincent Lefevre
Dans l'article <f6o3rd$1sfm$,
Marc Espie écrit:

Deux contraintes a prendre en compte:
- les entetes standards ne le sont pas toujours autant qu'on le voudrait.
J'ai vu pas mal de systeme ou il faut rajouter des incantations comme
#define _XOPEN_SOURCE pour avoir certaines des fonctions que l'on voudrait,
et ou on se retrouve generalement avec des fonctions que l'on ne voudrait
pas... bonjour les collisions.


Le problème est que pour certaines options (e.g. vérification complète
des assertions), on a besoin de l'en-tête <stdio.h> un peu partout.
Donc s'il y a une collision possible, autant la résoudre en changeant
le nom des variables. On le fait déjà pour "near", "far" et "pm", qui
sont des mots-clés avec certains compilateurs C. Je vais donc inclure
<stdio.h> dans le "mpfr.h". Idem pour <limits.h> (qui est déjà inclus
dans tous les fichiers avec les options par défaut du configure et
directement nécessaire pour un nombre non négligeable de fichiers .c).
En fait, pour ces deux en-têtes, on les a oubliés déjà plusieurs fois,
notamment à cause de leur inclusion conditionnelle. En effet, il est
un peu trop facile d'oublier un en-tête quand on utilise des macros
du style INT_MAX dans des macros qu'on définit nous-mêmes et que le
problème n'est pas détecté à la compilation.

Je vais laisser les autres en-têtes définis en local (cela concerne
surtout <stdlib.h> et <string.h>), tout du moins pour le moment.

- on n'utilise rarement une bibliotheque toute seule... si elle envahit
plus que le strict minimum de l'espace de noms global, alors il est a peu
pres certain que tot ou tard, deux bibliotheques vont se marcher sur les
pieds... particulierement pour les typedef et les struct. La solution serait
peut-etre de tout prefixer d'un mpfr_ ou equivalent, mais c'est un peu
lourd.


Euh... on le fait déjà pour tout ce que définit MPFR. Mais préfixer,
c'est impossible pour la bibliothèque standard et pour les bibliothèques
qu'on ne contrôle pas.

Si pour une toute petite partie du code, on doit dépendre d'en-têtes
particuliers, les définir en local ne poserait pas de problème (ou
alors modulariser le code).

--
Vincent Lefèvre - Web: <http://www.vinc17.org/>
100% accessible validated (X)HTML - Blog: <http://www.vinc17.org/blog/>
Work: CR INRIA - computer arithmetic / Arenaire project (LIP, ENS-Lyon)

1 2