IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

 C Discussion :

compilation de prog multi-modules (bis)


Sujet :

C

  1. #1
    Membre habitué
    Profil pro
    amateur
    Inscrit en
    Avril 2012
    Messages
    145
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : amateur
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Avril 2012
    Messages : 145
    Points : 144
    Points
    144
    Par défaut compilation de prog multi-modules (bis)
    Bonjour,

    Je reviens différemment sur ce thème, déjà évoqué dans un précédent fil: http:[i]http://www.developpez.net/forums/d12...multi-modules/. Désolé pour ce message très long, mais j'essaie de faire le tour de la question en procédant logiquement, dans la mesure de mes moyens, et en abordant les divers aspects souvent laissés implicites du sujet, ainsi que les liens (sic!) entre ces aspects.

    J'essaie de comprendre:
    1. Les rôles respectifs des fichiers sources .c et .h .
    2. Les rôles respectifs de l'inclusion (#include) et du "liage" explicite.
    Je veux par ce dernier point l'indication explicite d'un nom de fichier (compilé) sur la ligne de commande d'édition de lien. Ce que je voudrais, au-delà de "comment faire?", arriver à comprendre assez clairement comment ça marche.

    Pour ça j'utilise le programme exemple suivant:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // prog.c
    #include "salut.c"   [i] cette ligne-là change *******
    int main () {
       salut () ;
       return 1 ;
    }
     
    // salut.c
    #include <stdio.h>
    void salut () {
       puts ("Salut au monde !") ;
    }
     
    // salut.h
    void salut () ;
    Exploration:
    * Sans la ligne Astérix (;-), pas de compil.
    * En incluant l'un ou l'autre salut.*, la compil roule.
    * En incluant salut.h seul, le liage plante.
    * En incluant salut.c seul, le liage et l'exécution roulent.
    * Inclure .c et .h à la fois ne change rien: ça roule.
    Alors l'édition de lien, là je la fais sans indication d'autre fichier que prog.o:
    gcc -Wall -o prog prog.c

    Déjà, je viens de réaliser réellement que l'incluson "physique" (sic!) des fichiers fait que le compilateur sait déjà presque tout sur la totalité de l'appli (sauf s'il y a incohérence entre une déclaration dans un fichier et la définiton correspondante ailleurs, ou son absence). Mais alors, que rajoute la distinction entre .c et .h? et celle entre compilation et édition de liens? Là, je pige plus trop.

    J'ai dans l'idée que les deux marchent ensemble, et qu'ils ne servent qu'à faire de la compilation séparée. Les .h auraient pour but d'empêcher le compilateur de râler lorsqu'on compile séparément un module qui utilise des fonctionnalités externes (tiens, à propos, extern, encore un sujet à comprendre), en lui disant en gros "fais moi confiance, ce truc-là existe et il est comme j'te dis".
    Très bien mais pour moi, sur une machine moderne, tant que le projet ne dépasse pas une certaine échelle, ça ne sert pas à grand chose. (Surtout que le compilo ne va pas recompiler les fichiers inchangés.) Et encore moins en phase de développement où l'optimisation (ce qui bouffe l'essentiel du temps de compil) est plus que secondaire.
    Le truc, donc, ce serait de mettre les déclarations uniquement dans le .h, et de compiler en incluant seulement cela. Ce qui allège et accélère le processus. Ensuite, au liage, au lieu d'inclure les .c (ce qui imposerait une recompilation), on les indique à l'éditeur de lien explicitement. Est-ce que mon raisonnement est correct?
    [HS: y a pas des mots français pour link, linker, linking? Pourquoi lier, lieur, liage (ou liaison ;-) ne sont-ils pas utilisés? On pourrait aussi dire re-lieur, re-lier rel-liage.]

    Pour en revenir à mes problèmes, il me semble qu'il n'y a pas de différence fonctionnelle entre l'inclusion à la compilation et le "reliage" a posteriori. Je veux dire par là que mon prog fonctionnera strictement pareil si j'inclue le .c (avec ou sans .h) ou si j'inclue le .h seul et "relie" ensuite le .c. Est-ce que j'ai raison?
    Je vérifie que ça marche, au moins (compil avec .h seul):

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    spir@ospir:~/prog/C/comp$ gcc -Wall -o prog salut.o prog.o
    spir@ospir:~/prog/C/comp$ ./prog
    Salut au monde !
    Alors est-ce que cette ligne de raisonnement est valable quelle que soit l'architecture du projet: je peux toujours soit inclure les .c (les fichiers qui contiennent les définitions explicites) (avec les .h si les déclarations sont exportées) à la compilation, soit inclure les .h (les fichiers qui contiennent les définitions explicites) uniquement et relier les .c à l'édition de lien seulement. Correct?

    J'ai encore un petit problème. Je modifie mon programme ainsi, ce qui exporte la définition de 'salutation':
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // prog.c
    #include "salut.h"
    int main () {
       salut () ;
       return 1 ;
    }
     
    // salut.c
    #include <stdio.h>
    #include "salut.h"
    void salut () {
       puts (salutation) ;
    }
     
    // salut.h
    #ifndef H_SALUT
    #define H_SALUT
    const char * salutation = "Salut au monde !";
    void salut () ;
    #endif
    Là, malgré le #ifndef dans salut.h, j'ai une erreur à l'édition de lien (qui comme plus haut indique salut.o explicitement):
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    spir@ospir:~/prog/C/comp$ gcc -Wall -o prog salut.o prog.o
    prog.o:(.data+0x0): multiple definition of `salutation'
    salut.o:(.data+0x0): first defined here
    C'est tout-à-fait logique si je comprends bien: du fait de la compilation séparée, prog.c et salut.c incluent tous deux salut.h, malgré la précaution du #ifndef. Correct? Mais je suis bien obligé de l'inclure dans chacun, pour compiler, non? Je peux aussi dans prog.c inclure salut.c au lieu de salut .h, mais c'est pas ça le but du jeu, car là je compile avec prog.c la totalité du programme, ce n'est plus de la compilation séparée en fait. Alors quelle est la méthode que je n'ai pas trouvée tout seul?

    Le but à terme pour moi est de définir la manière la plus pratique d'utiliser les outils C avec mon style de programmation "exploratoire". Ce style, en gros, du point de vue compilation, c'est que chaque fichier source peut être lancé à tout moment (en fonction de là où j'en suis dans mon développement) pour tester ses fonctionnalités ou son comportement exact, ou encore pour diagnostic. Typiquement (c'est une vision schématique), chacun de mes fichiers se termine par qq ch du genre:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    void test_xxx () { ... }
    void test_yyy () { ... }
    void test_zzz () { ... }
    void test () {
       // "hors-commenter" ce que je ne teste pas là maintenant
       test_xxx () ;
       test_yyy () ;
       test_zzz :
    }
    // "hors-commenter" pour inclusion par un autre fichier source
    void main () {
       test () ;
       exit (1) ;
    }
    Il me semble que les outils genre makefile sont faits pour tout autre chose. En gros, pour moi, ils serviraient plutôt à distribuer le code en préparant la compilation par l'usager (utilisateur final ou autre programmeur) de l'application complète (ou du composant logiciel complet). Mais je me trompe peut-être totalement, peut-être que les makefiles sont tout-à-fait capables de permettre de programmer à ma façon.
    Sinon, je suis prêt à écrire des outils moi-même. Par exemple, un bout de code qui lirait les #include dans les en-têtes de mes fichiers source, récursivement, en les cherchant dans une arborescence locale, pour éditer un fichier batch de compilation / édition de liens / exécution test.
    Quelle méthode recommanderiez-vous?

    2 autres points que j'ai en-tête d'explorer sont:
    * Les différents type de fichiers à inclure prédéfinis par C ou par les outils C, et notamment pourquoi/comment l'éditeur de liens trouve les libs standards, et le sens concret de cette différence de syntaxe d'inclusion (<...> ou "...").
    * Le(s) bon(s) usage(s) d'un makefile.

    PS: J'ai dans l'idée de retracer ce fil de discussion pour un tutoriel sur le(s) sujet(s). Qu'en pensez-vous?

    Merci à tous,
    Denis

  2. #2
    Membre expert
    Avatar de kwariz
    Homme Profil pro
    Chef de projet en SSII
    Inscrit en
    Octobre 2011
    Messages
    898
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Moselle (Lorraine)

    Informations professionnelles :
    Activité : Chef de projet en SSII
    Secteur : Conseil

    Informations forums :
    Inscription : Octobre 2011
    Messages : 898
    Points : 3 352
    Points
    3 352
    Par défaut
    Salut,

    tout d'abord, il y a déjà un cours disponible sur la compilation séparée sur dvp : http://melem.developpez.com/tutoriel...ation-separee/, et un autre sur makefile http://gl.developpez.com/tutoriel/outil/makefile/.

    Sinon, il faut bien te familiariser avec la processus de "j'ai une collection de sources c" pour arriver à "j'ai un exécutable". Cela peut être aussi simple que "gcc -o monprog monprog.c" que plus complexe et caché comme "configure && make" ou cliquer sur l'option Build du menu Project de ton IDE préféré. Mais quelle que soit la méthode, ce qui se passe derrière est toujours identique et en très gros se passe ainsi :

    * tu crées un fichier source ".c"
    * le préprocesseur applique des modifications à ton fichier source, il inclut tous les fichiers des #include {la différence entre <> et "" ainsi que les différentes options disponibles en ligne de commande (-I -idirafter .... ) sont expliquées dans la doc http://gcc.gnu.org/onlinedocs/cpp/ }, il remplace texto les #define, évalue les #if et cie et produit un fichier qu'il passe au compilateur.
    * le compilateur fait le gros du boulot (?), il traduit le code source préprocessé en quelque chose qui sera utilisable par l'éditeur de lien : le fichier objet ".o". Les options sont décrites dans la doc (immense) de gcc par exemple http://gcc.gnu.org/onlinedocs/gcc-4.6.3/gcc/.
    * le linker/éditeur de liens (http://sourceware.org/binutils/docs/ld/) lui se charge de prendre plusieurs fichiers objets, de vérifier que tout ce qui est appelé est bien défini ou dans un ".o" ou dans une bibliothèque statique ".a" ou un bibliothèque dynamique ".so" et crée un fichier exécutable.

    Tout ça est caché derrière un frontend : gcc.

    Ensuite :

    * le loader charge l'exécutable, vérifie les liaisons dynamiques, et lance l'exécution qui commence par l'initialisation de la libc pour le programme, puis l'appel à main.

    Cela dit, il faut voir l'approche modulaire du c "simplement" :

    un source ".c" est une collection de fonctions qui ont un lien logique entre elles. Par exemple liste.c contiendra toutes les fonctions de gestion d'une liste.

    un header ".h" regroupe toutes (et normalement uniquement) les définitions nécessaires pour pouvoir utiliser la/les fonctionnalité(s) proposées pour le ".c". liste.h fournira la définition d'un type liste_t, de fonction permettant la manipulation d'une liste, mais cachera des fonctions uniquement utiles en interne à liste.c (par exemple la définition et la déclaration de tout ce qui concerne les noeuds).

    Tu peux voir ça comme un équivalent de l'objet liste est implémenté dans le .c et son interface est définie dans le .h

    Inclure un .h dans un source ne fait donc qu'inclure des définitions, pas l'implémentation qui elle se trouve sous forme de source dans le .c et sous forme compilée dans le .o
    C'est pourquoi à l'édition des liens pour créer un exécutable il faut fournir toutes les implémentations (=tous les .c ou tous les .o).

    Il existe donc des dépendances entre les différents fichiers. Ces dépendances sont décrites dans un Makefile. Par exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    monprog: monprog.o liste.o
      <la/les commandes pour lier monprog.o et liste.o>
     
    monprog.o: monprog.c liste.h
      <la/les commandes pour compiler monprog.c>
     
    liste.o: liste.c liste.h
      <la/les commandes pour compiler liste.c>
    Tu dis en substance que :
    pour créer l'exécutable monprog il te faut deux modules : monprog.o et liste.o
    monprog.o dépend de monprog.c (en effet si tu modifies ton source tu dois recompiler) et liste.h (si l'interface est modifiée alors tu dois recompiler aussi)
    liste.o dépend de liste.c (bah oui, l'implémentation) et de liste.h (l'interface)

    L'idée est que si tu ne modifies que ton source monprog.c, tu ne vas devoir recompiler (=produire un .o) liste.c puisque tout y est encore valable (l'interface n'a pas changée) : gain de temps (entre autre). Makefile se base sur les heures de modifications des fichiers. D'autres outils sont plus développés ou mieux adapté à d'autres environnements. Makefile est très puissant mais très vieux aussi

    Il existe également une option gcc qui permet de générer ces dépendances. Tu peux jeter sur le makefile "générique" que j'utilise pour de petits projets one-shot (Générateur de combinaisons), tu pose le Makefile dans un répertoire, tu mets tes sources dans un répertoire src, tu tunes un peu le Makefile (les libs et options de compil) et tout le reste est géré tant que la complexité du projet n'est pas trop grande.

  3. #3
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 409
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 48
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 409
    Points : 23 804
    Points
    23 804
    Par défaut
    Bonjour,

    Tes suppositions initiales sont les bonnes. L'usage veut que que les fichiers objets produit par le compilateur soit associés à des fichiers *.c et que les *.h ne servent qu'aux déclarations.

    Par contre, il te faut savoir que le compilateur ne fait pas en soi le distingo entre les différents types de fichiers : tu peux inclure en cascade n'importe quel fichier et il essaiera d'en compiler le contenu en continu comme s'il s'agissait d'un flux unique.


    1. Pour répondre à une de tes questions, il est tout-à-fait normal qu'un fichier *.c importe ses propres déclarations car, dedans, on peut y trouver :

    • Les prototypes de fonctions ;
    • Les macros #define qui servent autant au code proprement dit qu'à celui qui va l'utiliser ;
    • Les déclarations de variables globales externes ;
    • Les définitions de types, comme les unions, les structures et les typedef associés.


    Les #define peuvent servir à fixer les constantes d'un programme. Par exemple, si tu utilises un buffer de taille fixe, il est normal que l'utilisateur connaisse sa taille, mais le programme lui-même doit aussi en être informé à la compil. S'il s'avère à l'usage qu'il est trop petit, il te suffit de modifier la ligne #define en question dans le *.h et de recompiler le tout pour régler le problème.


    2. Par ailleurs, comme dit plus haut, le code C compilé est censé être vu comme un flux unique. Ça veut dire que si tu utilises une fonction qui n'est défini que plus loin, le compilateur doit être averti à l'avance de son existence ! Les compilateurs modernes (enfin, ceux qui ont moins de 25 ans :-) sont tous capables de supposer d'eux-mêmes que cette fonction va effectivement être définie et de compiler en silence, quitte à relire le fichier une seconde fois… si c'est possible ! Ça ne l'est pas si le fichier provient d'une bande ou d'une tube.

    La solution consiste donc à mettre tous les prototypes de fonction en début de fichier pour ne pas être embêté et le plus simple pour faire cela, encore une fois, consiste à inclure son propre *.h

    3. Si tu mélanges C et C++, ce qui est assez fréquent quand on débute, alors il est possible que tu sois tombé sur du code template. Ça, c'est une des raisons qui peuvent effectivement pousser un codeur à mettre du véritable code dans un fichier *.h. Dans ce cas, il vaut mieux que ce soit explicitement indiqué, et bien séparé du reste.

  4. #4
    Expert éminent sénior
    Avatar de diogene
    Homme Profil pro
    Enseignant Chercheur
    Inscrit en
    Juin 2005
    Messages
    5 761
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Enseignant Chercheur
    Secteur : Enseignement

    Informations forums :
    Inscription : Juin 2005
    Messages : 5 761
    Points : 13 926
    Points
    13 926
    Par défaut
    Mais on fait toujours de la compilation séparée en pratique : même en n'ayant écrit qu'un seul fichier source, l'éditeur de liens aura besoin des modules de bibliothèque qui ont déjà été compilés pour nous. Composer son programme à partir de plusieurs unités de compilation (un module .c et (éventuellement) son .h associé) ne fait qu'utiliser ce principe à notre bénéfice.

    1* Que doit contenir le .c :
    - le code des fonctions. Si les fonctions sont à usage purement interne au module elles seront déclarées static (les fonctions sont extern par défaut)
    - la définition de variables globales (si il y en a)
    - des définitions de type de données ou de constantes à usage purement interne au module
    - On incluera aussi le .h associé (si il existe) pour assurer la cohérence de l'unité de compilation.
    - des include de .h , uniquement ceux qui correspondent au contenu du .c
    - il n'a pas normalement à inclure d'autres .c

    2* Que doit contenir le .h associé :
    - Le prototype des fonctions définies dans le .c et utilisables dans d'autres unités de compilation
    - des déclarations extern de celles des variables globales définies dans le .c qui sont susceptibles d'être utilisées par d'autres unités de compilation
    - des définitions de type de données ou de constantes susceptibles d'être utilisées par d'autres unités de compilation
    - des include de .h, uniquement ceux qui sont correspondent au contenu du .h
    - Il ne doit PAS contenir de définition de variables (parce qu'il a vocation à être inclus dans plusieurs autres unité de compilation ce qui aboutirait à de multiples définitions).

    Le programme exécutable est alors obtenu en deux étapes :

    - Chaque unité du programme est compilée indépendamment des autres. Après compilation, il reste des symboles non résolus (fonctions ou variables globales définies dans une autre unité...)

    - L'éditeur de liens (link editor) est chargé ensuite de composer tous les modules et ceux des bibliothèques utilisées. Il fait les liaisons entre les symboles non résolus et leur emplacement dans le code. A ce stade, il faut spécifier tout ce dont a besoin le programme (explicitement ou par défaut) sinon on se retrouvera avec des symboles non résolus (les include dans le programme ne peuvent le faire par défaut : ils ne font qu'une inclusion de fichier).

  5. #5
    Membre habitué
    Profil pro
    amateur
    Inscrit en
    Avril 2012
    Messages
    145
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : amateur
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Avril 2012
    Messages : 145
    Points : 144
    Points
    144
    Par défaut double inclusion de fichier header
    Merci pour toutes vos réponses, ça clarifie bien les choses.

    Alors, j'ai toujours pas compris comment éviter d'inclure plusieurs fois un fichier .h. Exemple:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    // prog.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    #include "circ.h"
     
    int main () {
       const float MAX_R = 999.9 ;
       srand (time (NULL)) ;
       float r = rand () * MAX_R / RAND_MAX ;
       float c = circ (r) ;
       printf ("rayon %.3f --> circonférence %.3f\n", r, c) ;
       return 1 ;
    }
     
    //salut.c
    #include <stdio.h>
    #include "circ.h"
     
    float circ (float r) {
       return TAU * r ;
    }
     
    // salut.h
    #ifndef H_SALUT
    #define H_SALUT
    const float TAU = 6.28 ;   // voir http://tauday.com/tau-manifesto ;-)
    float circ (float rayon) ;
    #endif
    Une fois les 3 fichiers compilés séparément, prog.c et circ.c incluent tous deux le code de circ.h. Il me semble que c'est nécessaire, puisque prog.c doit avoir la déclaration de la fonction circ() et circ.c doit avoir celle de TAU. Correct?
    Donc forcément, lorsque l'on relie tout ça, ça donne des erreurs. Voilà ma commande et son résultat:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    spir@ospir:~/prog/C/comp$ gcc -Wall -o prog prog.o circ.o
    circ.o:(.rodata+0x0): multiple definition of `TAU'
    prog.o:(.rodata+0x0): first defined here
    collect2: ld returned 1 exit status
    Bon, je concède que l'exemple est un peu artificiel, car ici TAU n'a pas besoin d'être exporté, donc il pourrait être dans circ.c. Mais imaginons... De toute façon, le cas général n'est-il pas que la code client (ici prog) inclue le header de son "fournisseur" (circ.h), et le fichier source .c de ce module (circ.c) inclue aussi ce même header ?

    Il y a un point qui m'échappe.

    EDIT: Les tutoriels sur la compil séparée et sur makefile ne m'aide, car dans aucun des deux le fichier source .c du "module" n'inclue son header.

    denis

  6. #6
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 409
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 48
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 409
    Points : 23 804
    Points
    23 804
    Par défaut
    Bonjour,

    Citation Envoyé par denispir Voir le message

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    // prog.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    #include "circ.h"
    
    int main () {
       const float MAX_R = 999.9 ;
       srand (time (NULL)) ;
       float r = rand () * MAX_R / RAND_MAX ;
       float c = circ (r) ;
       printf ("rayon %.3f --> circonférence %.3f\n", r, c) ;
       return 1 ;
    }
    
    //salut.c
    #include <stdio.h>
    #include "circ.h"
    
    float circ (float r) {
       return TAU * r ;
    }
    
    // salut.h
    #ifndef H_SALUT
    #define H_SALUT
    extern const float TAU;   // voir http://tauday.com/tau-manifesto ;-)
    float circ (float rayon) ;
    #endif
    Il faut ajouter « extern » à l'endroit indiqué (et virer l'initialisation avec « = »).

    Comme on l'a dit plus haut, le compilateur interprète le code source provenant des différents fichiers inclus comme un flot continu sans se soucier des fichiers eux-mêmes.

    Lorsque tu écris « const float TAU = », ce n'est pas une directive à l'intention du compilateur que tu rédiges (utiliser #define pour cela), mais c'est une variable globale que tu instancies et qui a une adresse en mémoire. « extern » sert justement à indiquer qu'une variable globale existe quelque part sans l'instancier. C'est un peu l'équivalent du prototype pour une fonction.

    Donc, tu fais une déclaration « extern » dans ton fichier *.h qui sera lue par tout le monde et tu instancies ta variable comme tu l'as fait mais dans un et un seul fichier *.c.

  7. #7
    Membre habitué
    Profil pro
    amateur
    Inscrit en
    Avril 2012
    Messages
    145
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : amateur
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Avril 2012
    Messages : 145
    Points : 144
    Points
    144
    Par défaut
    Citation Envoyé par Obsidian Voir le message
    Bonjour,



    Il faut ajouter « extern » à l'endroit indiqué (et virer l'initialisation avec « = »).
    Ouais! ça marche, merci. (J'avais bien remarqué ce mot-clé "extern", mais comme j'en avais pas alors l'usage je l'ai ensuite oublié.)

    Comme on l'a dit plus haut, le compilateur interprète le code source provenant des différents fichiers inclus comme un flot continu sans se soucier des fichiers eux-mêmes.

    Lorsque tu écris « const float TAU = », ce n'est pas une directive à l'intention du compilateur que tu rédiges (utiliser #define pour cela), mais c'est une variable globale que tu instancies et qui a une adresse en mémoire. « extern » sert justement à indiquer qu'une variable globale existe quelque part sans l'instancier. C'est un peu l'équivalent du prototype pour une fonction.

    Donc, tu fais une déclaration « extern » dans ton fichier *.h qui sera lue par tout le monde et tu instancies ta variable comme tu l'as fait mais dans un et un seul fichier *.c.
    Je viens de faire quelques manips supplémentaires qui me donnent les résultats attendus, en ce qui concerne la redéclaration d'éléments du programme (suite à double inclusion de .h, ou autre):
    * type, complet ou non: si les 2 déclas sont cohérentes, tout va bien
    * prototype de func: même chose
    * valeur const: utiliser "extern"
    * #define: accepté plusieurs fois, si contenu pas égal, gcc râle (à bon escient)

    Y a juste un petit détail, je n'arrive pas à utiliser un type de struct déclaré dans un header incomplètement (pour masquer le détail à l'export), avec un typedef ou non. Lorsque je tente de l'utiliser, gcc me dit "storage size of <type> not known". Et il a raison!

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    // bidon.c
    typedef struct bidon_t { int i ;} bidon ;
     
    // bidon.h
    typedef struct bidon_tbidon ;
     
    // prog.c
    #include "bidon.h"
     
    int main () {
       struct bidon_t b1 ;
       bidon b2 ;
    }
    gcc -Wall -c "prog.c" (in directory: /home/spir/prog/C/comp)
    prog.c: In function ‘main’:
    prog.c:8:19: error: storage size of ‘b1’ isn’t known
    prog.c:9:10: error: storage size of ‘b2’ isn’t known


    Enfin: c'est bien beau de trouver le moyen de faire accepter des déclarations en double, mais le but c'est plutôt d'éviter de multiples inclusions, non? Ne serait-ce que parce que ça bouffe de l'espace pour rien.) Le truc
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    #ifndef CODE_HEADER
    #defineCODE_HEADER
    ..................................
    #endif
    est super, mais y a-t-il moyen de le faire marcher avec la compilation séparée? ou y a-t-il un autre truc?

    Denis

  8. #8
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 131
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 131
    Points : 33 072
    Points
    33 072
    Billets dans le blog
    4
    Par défaut
    Le mot-clé extern n'a rien à voir avec const.
    Il indique qu'une telle variable existe, mais est définie ailleurs, dans un .c .
    Ca veut juste dire que si le compilateur rencontre une variable avec ce nom, il sait qu'elle existe et est utilisable, bien qu'ignorant réellement son origine. Le linker s'en chargera.

    Y a juste un petit détail, je n'arrive pas à utiliser un type de struct déclaré dans un header incomplètement (pour masquer le détail à l'export), avec un typedef ou non.
    Parce que tu confonds déclaration et implémentation.
    Dans le header du module, tu dois implémenter la structure complète.. Autrement comment les autres modules/programmes qui vont l'inclure sauront-ils ce qu'elle contient ?
    La seule raison de ne pas l'implémenter c'est pour réaliser une forward declaration.
    Pourquoi ? parce que le compilateur du programme, tu veux initialiser une structure, s'il n'a jamais vu son implémentation complète, il la connait comment ?
    Sauf si tu n'utilises que des pointeurs sur la structure et fournis toutes les fonctions pour utiliser ce pointeur.

    mais y a-t-il moyen de le faire marcher avec la compilation séparée? ou y a-t-il un autre truc?
    Il s'agit de code préprocesseur, ça n'a rien à voir avec le fait que la compilation soit séparée.
    Si justement la compilation est séparée, tu compiles d'abord le module, la compilation du programme qui utilise ce module doit inclure son header et les define guard prennent tout leur sens.

    c'est bien beau de trouver le moyen de faire accepter des déclarations en double, mais le but c'est plutôt d'éviter de multiples inclusions, non?
    Oui c'est le but, et ça le réalise parfaitement. Que veux-tu d'autre ?
    On n'évite pas les déclarations en double, on évite la double inclusion.
    Mais ton fichier devra bien être inclus par tous ceux qui en ont besoin, et s'il faut prêter attention aux includes circulaire, ceci reste une excellente protection, voire la seule possible.

  9. #9
    Expert éminent sénior
    Avatar de diogene
    Homme Profil pro
    Enseignant Chercheur
    Inscrit en
    Juin 2005
    Messages
    5 761
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Enseignant Chercheur
    Secteur : Enseignement

    Informations forums :
    Inscription : Juin 2005
    Messages : 5 761
    Points : 13 926
    Points
    13 926
    Par défaut
    Y a juste un petit détail, je n'arrive pas à utiliser un type de struct déclaré dans un header incomplètement (pour masquer le détail à l'export), avec un typedef ou non. Lorsque je tente de l'utiliser, gcc me dit "storage size of <type> not known". Et il a raison!
    C'est pourquoi le type utilisé doit être un pointeur sur X et X lui-même est opaque. Naturellement, il faut prévoir des fonctions de construction, d'utilisation et de destruction d'un X.

    Par exemple, le type standard FILE est opaque. pour l'utiliser on passe par
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
      FILE * f = fopen(...) // Création et initialisation du FILE
      fread(f,.....)       // manipulation du FILE
      fclose(f);            // destruction du FILE
    Ce qui peut donner :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // bidon.c
    #include "bidon.h"
    struct bidon_t { int i ;};
    bidon * creationBidon(void){....}
    ....
     
    // bidon.h
    typedef struct bidon_t bidon ;
    bidon * creationBidon(void);
    ....
     
    // prog.c
    #include "bidon.h"
     
    int main () {
       bidon * b = creationBidon() ;
    ....
    }

  10. #10
    Membre expert
    Avatar de kwariz
    Homme Profil pro
    Chef de projet en SSII
    Inscrit en
    Octobre 2011
    Messages
    898
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Moselle (Lorraine)

    Informations professionnelles :
    Activité : Chef de projet en SSII
    Secteur : Conseil

    Informations forums :
    Inscription : Octobre 2011
    Messages : 898
    Points : 3 352
    Points
    3 352
    Par défaut
    Citation Envoyé par denispir Voir le message
    Ouais! ça marche, merci. (J'avais bien remarqué ce mot-clé "extern", mais comme j'en avais pas alors l'usage je l'ai ensuite oublié.)
    Il faut bien faire la différence entre une déclaration (je dis que ça existe mais je n'en donne aucun détail) et une définition (je dis exactement ce que c'est).

    ne déclare ni ne définit une constante. Il s'agit d'une déclaration signifiant que quelque part dans un autre module il sera définit une variable accessible dont le type est double et l'identifiant est TAU. Le compilateur à ce moment ne réserve aucune place pour cette variable car il sait (enfin tu lui dis) que cela sera fait ailleurs.


    Y a juste un petit détail, je n'arrive pas à utiliser un type de struct déclaré dans un header incomplètement (pour masquer le détail à l'export), avec un typedef ou non. Lorsque je tente de l'utiliser, gcc me dit "storage size of <type> not known". Et il a raison!

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    // bidon.c
    typedef struct bidon_t { int i ;} bidon ;
     
    // bidon.h
    typedef struct bidon_tbidon ;
     
    // prog.c
    #include "bidon.h"
     
    int main () {
       struct bidon_t b1 ;
       bidon b2 ;
    }
    gcc -Wall -c "prog.c" (in directory: /home/spir/prog/C/comp)
    prog.c: In function ‘main’:
    prog.c:8:19: error: storage size of ‘b1’ isn’t known
    prog.c:9:10: error: storage size of ‘b2’ isn’t known
    Quand tu définis une variable d'un certain type, le compilateur doit connaître la taille du type pour réserver la mémoire. S'il ne la connaît pas il émet une erreur. Pour cacher ton implémentation le plus simple est de passer par un pointeur sur la structure à cacher (ou directement en faisant un typedef sur void*, tu caches tout). Un pointeur sur quelque chose à toujours une taille de pointeur, quel que soit ce quelque chose : le compilateur peut donc réserver l'espace pour un pointeur.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    /* bidon.h */
    typedef struct bidon_t* Bidon;
    /* à partir d'ici tu peux utiliser Bidon sans erreur
     * la variante
     * typedef struct bidon_t bidon;
     * te permettra d'utiliser bidon* sans problèmes
     */
    Enfin: c'est bien beau de trouver le moyen de faire accepter des déclarations en double, mais le but c'est plutôt d'éviter de multiples inclusions, non? Ne serait-ce que parce que ça bouffe de l'espace pour rien.) Le truc
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    #ifndef CODE_HEADER
    #defineCODE_HEADER
    ..................................
    #endif
    est super, mais y a-t-il moyen de le faire marcher avec la compilation séparée? ou y a-t-il un autre truc?

    Denis
    Heu ... c'est pour faire de la compilation séparée que ça existe ... je dois mal comprendre ta question ...

    Un truc : avec gcc essaye l'option -E comme :
    Il va t'afficher le flux qui est compilé, celui qui est généré par la préprocesseur.
    Tu dois voir toutes les inclusions, #define remplacés, ... En un mot : ce que le compilateur compile réellement.

  11. #11
    Membre habitué
    Profil pro
    amateur
    Inscrit en
    Avril 2012
    Messages
    145
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : amateur
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Avril 2012
    Messages : 145
    Points : 144
    Points
    144
    Par défaut
    Citation Envoyé par kwariz Voir le message
    Quand tu définis une variable d'un certain type, le compilateur doit connaître la taille du type pour réserver la mémoire. S'il ne la connaît pas il émet une erreur. Pour cacher ton implémentation le plus simple est de passer par un pointeur sur la structure à cacher (ou directement en faisant un typedef sur void*, tu caches tout). Un pointeur sur quelque chose à toujours une taille de pointeur, quel que soit ce quelque chose : le compilateur peut donc réserver l'espace pour un pointeur.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    /* bidon.h */
    typedef struct bidon_t* Bidon;
    /* à partir d'ici tu peux utiliser Bidon sans erreur
     * la variante
     * typedef struct bidon_t bidon;
     * te permettra d'utiliser bidon* sans problèmes
     */
    D'accord. Je crois que, dans un premier temps, je vais systématiquement "pointer" les types struct, même celles qui ont une sémantique de donnée proprement dite (comme une couleur, par opp. à celles qui ont une sémantique de "chose", comme un pot de couleur). Le overhead est minime (déref) et ça permet d'avoir une interface uniforme et de ne pas ses soucier du coût du passage en paramètre. Qu'en pensez-vous?

    Heu ... c'est pour faire de la compilation séparée que ça existe ... je dois mal comprendre ta question ...

    Un truc : avec gcc essaye l'option -E comme :
    Il va t'afficher le flux qui est compilé, celui qui est généré par la préprocesseur.
    Tu dois voir toutes les inclusions, #define remplacés, ... En un mot : ce que le compilateur compile réellement.
    Alors, c'est ce que je tente maladroitement d'expliquer plus haut : lorsque je fais la compil séparée, chacun des deux fichiers source (prog.c et circ.c) incluent physiquement circ.h. Il n'y a pas moyen de l'empêcher puisque justement les compils sont faites à part. Le truc de #ifndef ne sert donc à rien, le compilo ne peut savoir qu'un autre fichier source inclue circ.h. Et c'est d'ailleurs pour ça que j'avais cette erreur avec la constante TAU.
    En revanche, si je compile et build tout à la fois, le compillateur voit que circ.h est déjà inclus lorsqu'il tombe sur le deuxième "#include circ.h" (et dans ce cas je n'avais pas l'erreur de constante). Je cherche donc comment faire la compil séparée (pour les avantages qu'elle procure) sans avoir les inclusions multiples.

    Denis

    PS: Je viens de commencer à expérimenter avec make: super! C'est simple, rapide, efficace, en tout cas pour des cas standard, appremment. Apparemment, aussi, je n'ai pas le problème de l'inclusion multiple. Mais je ne comprends pas pourquoi.

    Denis

  12. #12
    Membre expert
    Avatar de kwariz
    Homme Profil pro
    Chef de projet en SSII
    Inscrit en
    Octobre 2011
    Messages
    898
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Moselle (Lorraine)

    Informations professionnelles :
    Activité : Chef de projet en SSII
    Secteur : Conseil

    Informations forums :
    Inscription : Octobre 2011
    Messages : 898
    Points : 3 352
    Points
    3 352
    Par défaut
    Citation Envoyé par denispir Voir le message
    Alors, c'est ce que je tente maladroitement d'expliquer plus haut : lorsque je fais la compil séparée, chacun des deux fichiers source (prog.c et circ.c) incluent physiquement circ.h. Il n'y a pas moyen de l'empêcher puisque justement les compils sont faites à part. Le truc de #ifndef ne sert donc à rien, le compilo ne peut savoir qu'un autre fichier source inclue circ.h. Et c'est d'ailleurs pour ça que j'avais cette erreur avec la constante TAU.
    En revanche, si je compile et build tout à la fois, le compillateur voit que circ.h est déjà inclus lorsqu'il tombe sur le deuxième "#include circ.h" (et dans ce cas je n'avais pas l'erreur de constante). Je cherche donc comment faire la compil séparée (pour les avantages qu'elle procure) sans avoir les inclusions multiples.

    Denis

    PS: Je viens de commencer à expérimenter avec make: super! C'est simple, rapide, efficace, en tout cas pour des cas standard, appremment. Apparemment, aussi, je n'ai pas le problème de l'inclusion multiple. Mais je ne comprends pas pourquoi.

    Denis
    OK ... un header ne doit contenir que des déclarations et pas de définitions. Attention tu n'as pas définit une constante TAU mais juste une variable globale ... elle reste modifiable. Pour faire ce genre de choses, tu mets dans le header la déclaration de la variable :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    /* circ.h */
     
    #ifndef CIRC_H__
    #define CIRC_H__
     
    extern double TAU;
    extern const double CTAU;
     
    #endif /* CIRC_H__ */
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    /* circ.c */
     
    #include "circ.h"
     
     
    double TAU=6.28;
    const double CTAU=6.283;
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    /* test.c*/
     
    #include "circ.h"
    #include <stdio.h>
     
    int main()
    {
      printf("TAU  = %f\n", TAU);
      TAU=3.14;
      printf("TAU  = %f\n", TAU);
     
      printf("CTAU = %f\n", CTAU);
      /*
      CTAU=3.14;
            provoque :
            $ gcc -c test.c
            test.c: In function ‘main’:
            test.c:11:3: error: assignment of read-only variable ‘CTAU’
            make: *** [test.o] Error 1
      */
      return 0;
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # Makefile
     
    test: test.o circ.o
            gcc -o test test.o circ.o
     
    test.o: test.c circ.h
            gcc -c test.c
     
    circ.o: circ.c circ.h
            gcc -c circ.c
    # Attention aux tabs
    Puis :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    $ make
    gcc -c test.c
    gcc -c circ.c
    gcc -o test test.o circ.o
    $ ./test 
    TAU  = 6.280000
    TAU  = 3.140000
    CTAU = 6.283000

  13. #13
    Membre habitué
    Profil pro
    amateur
    Inscrit en
    Avril 2012
    Messages
    145
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : amateur
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Avril 2012
    Messages : 145
    Points : 144
    Points
    144
    Par défaut programmation exploratoire
    Bon, je réponds moi-même à la partie de ma question sur ce sujet. J'ai trouvé un moyen très pratique avec make.

    Mon objectif était de pouvoir compiler en exécutable tout fichier source d'un projet, indépendamment et évidemment sans avoir à taper la commande avec toutes les dépendances à chaque (ou réécrire un batch, ou quoi que ce soit).

    Le truc, c'est juste d'ajouter dans le makefile une "cible" de build pour chaque fichier source (ou au fur et mesure qu'ils commencent à être testable. Dans le cas de mon petit projet test, ça donne donc un makefile avec une seule entrée supplémentaire, celle nommée "circ":

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    CFLAGS=-Wall -ftabstop=3
    LDFLAGS=
    # CC=gcc
    # EXEC=prog
     
    all: prog
     
    prog: circ.o prog.o
    	$(CC) circ.o prog.o -o prog $(LDFLAGS)
     
    circ: circ.o
    	$(CC) circ.o -o circ $(LDFLAGS)
     
    prog.o: prog.c
    	$(CC) -c prog.c -o prog.o $(CFLAGS)
     
    circ.o: circ.c circ.h
    	$(CC) -c circ.c -o circ.o $(CFLAGS)
     
    clean:
    	rm *.o
     
    mrproper: clean
    	rm prog
    J'ai remplacé dans mon éditeur (geany) les commandes standard de compil et build (qui appellent directement gcc) par un usage de make
    où %e représente le nom du fichier (sans extension) : ça appelle donc la "cible" correspondante du makefile. Et voilà! Pour tester le fichier courant, 2 ou 3 touches (compil -> build -> exec).

    Tout commentaire ou astuce bienvenu(e).

    Merci à vous,
    Denis

  14. #14
    Membre expert
    Avatar de kwariz
    Homme Profil pro
    Chef de projet en SSII
    Inscrit en
    Octobre 2011
    Messages
    898
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Moselle (Lorraine)

    Informations professionnelles :
    Activité : Chef de projet en SSII
    Secteur : Conseil

    Informations forums :
    Inscription : Octobre 2011
    Messages : 898
    Points : 3 352
    Points
    3 352
    Par défaut
    Citation Envoyé par denispir Voir le message
    Mon objectif était de pouvoir compiler en exécutable tout fichier source d'un projet, indépendamment et évidemment sans avoir à taper la commande avec toutes les dépendances à chaque (ou réécrire un batch, ou quoi que ce soit).
    Ce n'est généralement pas possible, car pour obtenir un exécutable il faut définir la fonction main. Si tu le fais dans prog.c et dans circ.c alors si tu essaye de linker les deux tu vas avoir deux fonctions main de définies -> erreur de link.
    Si tu ne définis la fonction main que dans prog.c et pas dans circ.c alors tu ne peux créer un exécutable uniquement avec circ.c -> erruer de link, main manque

    Attention, prog.o dépend non seulement de prog.c mais aussi de circ.h (si l' interface change en général il faut recompiler).

    Edit: oui ... l'option -ftabstop ... un conseil pour les sources C transforme tout les tab en espaces c'est beaucoup plus simple (mais surtout pas pour le Makefile).

  15. #15
    Membre habitué
    Profil pro
    amateur
    Inscrit en
    Avril 2012
    Messages
    145
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : amateur
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Avril 2012
    Messages : 145
    Points : 144
    Points
    144
    Par défaut
    Citation Envoyé par kwariz Voir le message
    Ce n'est généralement pas possible, car pour obtenir un exécutable il faut définir la fonction main. Si tu le fais dans prog.c et dans circ.c alors si tu essaye de linker les deux tu vas avoir deux fonctions main de définies -> erreur de link.
    Si tu ne définis la fonction main que dans prog.c et pas dans circ.c alors tu ne peux créer un exécutable uniquement avec circ.c -> erruer de link, main manque
    Ah oui, mais il y a une solution toute simple pour ça aussi (j'ai oublié de le mentionner dans le dernier message): un fichier source (qui n'est pas le main d'une app) se termine ainsi (juste un exemple bidon, mais qui tourne):

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #define TEST
    #ifdef TEST
    void test_circ () {
       const float TOLERANCE = 0.001 ;
       const float result = TAU * 3.0 ;
       assert (circ(3.0) - result < TOLERANCE) ;
       printf ("circonférence test: %f\n", result) ;
    }
    // autres fonctions test
     
    void test () {
       test_circ () ;
       // autres appels de fonctions test
    }
    int main () {
       test () ;
       return 0 ;
    }
    #endif
    Autrement dit, la section de test unitaire se retrouve encadrée d'un bloc #ifdef...#endif conditionné par une macro TEST. Il suffit de commenter cette macro pour permettre l'inclusion de ce fichier, lorsqu'on en build et teste un autre. Ou, tout simplement, dès que celui-ci est (temporairement) au point.
    Bon, je parle un peu en l'air, là, parce que cette méthode je l'ai pas encore pratiquée sur la durée, mais à priori elle me séduit bien et en tout cas reflète tout-à-fait ma façon de coder.
    Qu'en pensez-vous? Et comment pratiquez-vous vous-mêmes, dans le quotidien, pour tester vos fonctionnalités, essais, modifs?

    Attention, prog.o dépend non seulement de prog.c mais aussi de circ.h (si l' interface change en général il faut recompiler).
    Exact ! Il ya un bug dans mon make file.


    Edit: oui ... l'option -ftabstop ... un conseil pour les sources C transforme tout les tab en espaces c'est beaucoup plus simple (mais surtout pas pour le Makefile).
    Oui, merci de l'avertissement. (on va pas entrer dans une discussion tab vs espaces, hein )
    Denis

  16. #16
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 131
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 131
    Points : 33 072
    Points
    33 072
    Billets dans le blog
    4
    Par défaut
    Je trouve que tu te prends vraiment la tête et que tes exemples, s'ils "fonctionnent" pour 2 fichiers, vont vraiment te rendre compte que dans un vrai projet de centaines de fichiers c'est galère.

    Il faut penser chaque module comme solitaire, éventuellement ayant besoin d'autre(s) module(s), et les compiler pour créer non pas un executable mais une lib (.a, .lib, .so, .dll), qui sera elle utilisée par un programme qui les utilise.

    En fait il te faut architecturer tes couches, tes modules, et, amha, prendre les bases du langage et comment fonctionne la compilation d'un projet de plusieurs fichiers, puis de plusieurs modules.
    C'est pas compliqué du tout, surtout avec les IDE récents.

    Et d'ailleurs, les TU ne sont donc rien d'autre qu'un executable qui utilise la lib du module qu'il doit tester.

  17. #17
    Membre habitué
    Profil pro
    amateur
    Inscrit en
    Avril 2012
    Messages
    145
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : amateur
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Avril 2012
    Messages : 145
    Points : 144
    Points
    144
    Par défaut [B][I]bug de compilation lié à fichier ;h;gch[/I][/B]
    Voilà, je n'arrivais plus à compiler quelque chose qui fonctionnait auapravant (un "module", avec .c et .h correspondant). Le problème était dû à l'existence d'un fichier .h.ghc. J'imagine que ces fichiers servent peut-être à gcc à éviter de re-compiler les .h, susceptibles d'être inclus plusieurs fois; le contenu d'un tel fichier est du binaire.

    Bon, je me doutais qu'il y avait un binz avec le header, pour ainsi dire "gelé"; vu que l'erreur était une incompatibilité de déclararions (que j'avais pourtant bien harmonisées dans les 2 sources), puis lorsque je l'ai à nouveau modifié (pour tenter de provoqier une re-compilation) les n° de lignes ne correspondaient plus. Dès que j'ai viré ce .h.gch, bingo! ça roule.
    Alors ce qui est bizarre, c'est pourquoi make ne prenait pas en compte la version plus récente du header? (bien cité dans les dépendances, et d'ailleurs ça a re-marché sans que je change le makefile)

    Ce que je voudrais, aussi, c'est pouvoir compiler à part ces header pour vérifier leur validité (en "autonomie"), avant de les inclure en compilant le(s) fichier(s) qui les citent. Il n' y a pas de problème de compile proprement dit, la commande est identique (et d'ailleurs il n'y a même pas besoin de préciser un nom de sortie) Le problème, c'est d'avoir une commande de compilation qui appelle une règle (générique ou spécifique) du makefile. Or, ces règles portent des noms de cibles et non pas de sources; ce qui fait que si je compilais ordinairement un .h, je produirais un .o, le nom de la cible correspondant; ce qui ne me semble pas sain du tout.

    C'est peut-être ça un fichier .h.gch, d'ailleurs. PS: *** je viens de vérifier: oui, si je compile un .h, gcc fait un .h.gch ***

    Denis

  18. #18
    Membre expert
    Avatar de kwariz
    Homme Profil pro
    Chef de projet en SSII
    Inscrit en
    Octobre 2011
    Messages
    898
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Moselle (Lorraine)

    Informations professionnelles :
    Activité : Chef de projet en SSII
    Secteur : Conseil

    Informations forums :
    Inscription : Octobre 2011
    Messages : 898
    Points : 3 352
    Points
    3 352
    Par défaut
    Salut,

    Les fichiers gch sont des headers précompilés, qui accélèrent la compilation de très gros projets.
    Je ne sais pas si c'est une bonne chose de vouloir compiler les headers pour vérifier qu'il sont syntaxiquement corrects, en fait je n'en vois pas l'intérêt immédiat. En effet, s'il y a une erreur elle est détectée lors de la compilation du projet, et si un header n'est pas utilisé, il ne devrait pas se trouver dans les sources.

  19. #19
    Membre habitué
    Profil pro
    amateur
    Inscrit en
    Avril 2012
    Messages
    145
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : amateur
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Avril 2012
    Messages : 145
    Points : 144
    Points
    144
    Par défaut
    Citation Envoyé par kwariz Voir le message
    Salut,

    Les fichiers gch sont des headers précompilés, qui accélèrent la compilation de très gros projets.
    Je ne sais pas si c'est une bonne chose de vouloir compiler les headers pour vérifier qu'il sont syntaxiquement corrects, en fait je n'en vois pas l'intérêt immédiat. En effet, s'il y a une erreur elle est détectée lors de la compilation du projet, et si un header n'est pas utilisé, il ne devrait pas se trouver dans les sources.
    D'ac, je vais tout simplement ne pas les compiler.

    Denis

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. compilation de programme multi-modules
    Par denispir dans le forum Débuter
    Réponses: 14
    Dernier message: 15/04/2012, 14h13
  2. [langage] compiler un prog perl
    Par mike21 dans le forum Langage
    Réponses: 20
    Dernier message: 23/11/2004, 18h57
  3. Comment compiler un prog Gtk sous Debian ?
    Par GLDavid dans le forum GTK+
    Réponses: 3
    Dernier message: 25/06/2004, 20h49
  4. compiler un prog open gl
    Par bobbyf dans le forum OpenGL
    Réponses: 2
    Dernier message: 11/03/2004, 09h22
  5. [TP]Compiler un prog sans entrer dans TP7
    Par poppels dans le forum Turbo Pascal
    Réponses: 11
    Dernier message: 23/10/2002, 19h46

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo