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 :

un peu perdu avec les storage class du C en revenant de java


Sujet :

C

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Futur Membre du Club
    Profil pro
    Inscrit en
    Janvier 2012
    Messages
    5
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2012
    Messages : 5
    Par défaut un peu perdu avec les storage class du C en revenant de java
    Salut,

    Je suis en train de programmer en C pour un microcontroleur de la famille PIC18 (avec le compilateur MCC18) et je n'avais plus fait de C depuis assez longtemps. Je suis beaucoup plus à l'aise avec java ou l'assembler directement. Et je me perd un peu avec les déclaration de variables static et extern ainsi que leur portée, suivant où elles sont déclarées. Je suis à l'aise avec les pointeurs, mais pas forcément avec les typedef union et struct.

    Pour m'y retrouver, j'essaie de séparer mon code source en différents fichiers, de manière vaguement équivalente à ce que j'aurais fait sous java (je sais que ce n'est pas très habituel de vouloir se rapprocher de la POO avec un langage non OO mais ça m'aide à structurer l'ensemble). Je me retrouve avec un .h et un .c par "objet" (si vous me permettez l'abus de langage). Et voici ce que je fais pour l'instant pour tenter d'avoir l'équivalent de variables public / private, static ou non.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    // java
    public static int i; // attribut de classe
     
    // C
    // .h
    extern int i;
    // .c (au minimum dans un seul .c mais n'importe lequel qui utilisera la viariable j'ai l'impression, ce qui me perturbe)
    int i;
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    //java
    private static int i; // attribut de classe
    
    // C
    // .c (uniquement dans "l'objet" qui l'utilise, c'est pas vraiment private mais la variable reste inaccessible pour le reste du programme)
    static int i;
    Jusque là, la correspondance entre java et C est-elle correcte ? Je ne connaissais pas "extern". Quelle est la portée exacte de la variable ?

    Ensuite, comment devrait-on faire pour les attributs non statiques ? Voici à quoi je pense
    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
    // java
    public int a,b; // attributs de classe
     
    // C
    // .h
    typedef struct {
        int a;
        int b;
    } objet;
    // .c (n'importe lequel qui utilise l'objet, du moment que ob est passé comme argument de fonction)
    // pour un attribut private, le typedef serait aussi dans le .c, avec les mêmes remarque que précédemment
    objet ob;
    ...
    ob.a = 0;
    f(ob); // etc ...
    Ici, ob est-il en fait un pointeur ?
    Si j'ai
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    objet obStack[10];
    objet ob;
    int i,j;
    ...
    ob = obStack[i];
    obStack[j] = ob;
    Je n'ai pas de certitudes sur le fonctionnement des 2 dernières lignes. Si ob et obStack[i] sont des pointeurs (avec l'indice pour obStack), la dernière serait particulièrement dangereuse. Mes tests ont eu des comportements différents à plusieurs reprises et après avoir regardé le résultat de la compilation en assembleur + utilisation de la RAM, j'ai parfois vu une duplication en 3 exemplaires (3 zones mémoires) pour une seule et même variable "static" qui aurait dû être sur une seule et même zone mémoire (l'utilisation de "extern" comme dans le premier exemple a résolu le problème dans ce cas). Je vois ça comme un problème lié à la manière de déclarer les variables.

    Bref, les correspondances que j'ai décrit sont-elles correctes ? Dans ces cas, quelle est la portée des variables (c'est généralement ça qui me pose souci je crois) ? Bref, si quelqu'un pouvait m'aider à me remettre les idées en place, je lui serais très reconnaissant.

    Merci d'avance !

  2. #2
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Chercheur d'emploi
    Inscrit en
    Septembre 2007
    Messages
    7 477
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur d'emploi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 477
    Par défaut
    Bonjour,

    Gérer un fichier *.c et un fichier *.h distincts sont effectivement la bonne façon de faire. Même si, en C, on a généralement tendance à mettre dedans tout ce qui concerne un thème précis pas simplement une classe au point de vue syntaxique et comme ça l'est imposé en Java.

    Par contre, le « *.h » est en principe fait pour ne recevoir que des déclarations. Rien ne doit être instancié en pratique lorsque le compilateur lit ce fichier (sauf rares exceptions qu'on ne détaillera pas ici). L'idée générale est que ce fichier sert de « mode d'emploi » aux autres parties d'un projet pour pouvoir compiler leur code sans avoir nécessairement besoin du tien. Donc, on va y mettre l'API, ce qui va principalement consister en les symboles #define, les définitions de types, les prototypes de fonctions et les variables externes. Les autres fichiers « *.c » peuvent alors inclure ce fichier pour avoir connaissance de l'existant et se baser dessus.

    « extern » sert à dire qu'une variable existe bien, mais pas ici. Ça implique également qu'« extern » ne s'utilise que sur des variables globales (lesquelles sont procrites à l'usage).

    Donc, si tu écris « int i; » en début de fichier, ta variable sera bel et bien instanciée et tu pourras l'utiliser. Par contre, si tu définis ce même nom de variable dans un autre fichier *.c et que tu les compiles de manière séparée, il y aura un conflit au moment ou tu essaieras de les réunir. En préfixant ta variable avec « extern », le compilateur te croit, laisse en blanc dans le code l'adresse effective de cette variable, et complètera les trous au moment de l'édition des liens.

  3. #3
    Expert confirmé
    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
    Par défaut
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    ob = obStack[i];
    obStack[j] = ob;
    Dans un tel code, il y a copie de la valeur (ici d'un objet) en opérande de droite dans l'objet en opérande de gauche (pas copie de l'adresse de l'objet en opérande de droite ou attribution à l'objet à gauche de l'adresse de l'objet de droite).
    Il faut que l'opérateur = soit défini pour l'objet en opérande de gauche.
    L'opérateur = est défini pour tous les objets sauf les tableaux.
    La valeur d'un objet est simplement l'information qu'il stocke sauf pour les tableaux où elle est l'adresse du premier élément du tableau.

  4. #4
    Futur Membre du Club
    Profil pro
    Inscrit en
    Janvier 2012
    Messages
    5
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2012
    Messages : 5
    Par défaut
    @Obsidian
    Pour l'utilisation des .h et des .c, je connais bien leur rôle (je sais aussi quand et pourquoi donner le détail de l'implémentation d'une fonction avec un #define dans un .h)

    Ici, j'utilise un "extern" là où j'aurais utilisé un singleton avec des méthodes et attributs statiques en java. Dans mon code, ces éléments statiques ne doivent pas bouger dans la RAM pour limiter la pagination (rester dans la même banque de 256 octets) et avoir un code mieux optimisé. En plus, ils sont utilisés par l'équivalent de plusieurs threads (loop dans le main et interruptions du µC) avec contrôle de l'accès pour qu'il n'y ait pas de souci d'écrasement. Bref, c'est utilisé à bon escient.

    Je veux juste m'assurer que les "équivalences" que j'ai décrit sont correctes avant de produire plus de code.

    Mais je suis surpris car après avoir déclaré la variable en extern dans un .h, je me retrouve obligé de la redéclarer une nouvelle fois (sans extern) dans un .c (et n'importe lequel en plus) sans quoi j'ai des problèmes de compilation. La déclarer en "static" simplement dans le .h ne fonctionne pas non plus. En comparaison du java, c'est très surprenant et je ne vois pas d'explication.
    A la limite, si j'avais eu besoin de la redéclarer dans chaque .c qui l'utilise, je me serais dit que c'était pour que le linker fasse l'association. Mais là, même si 15 .c vont utiliser la même variable, je dois ne la redéclarer qu'une seule fois dans un seul des .c au hasard parmi les 15 :megastrange:


    @diogene
    obStack (sans l'indice) est un pointeur, ça je savais, on est d'accord.
    Et selon toi, même si ob est d'un type un peu complexe (typedef struct), écrire ob1 = ob2 sera transformé en ob1.a = ob2.a et ob1.b = ob2.b.

    Que se passe-t-il avec des typedef union ? J'en utilise beaucoup. Par 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
    typedef union 
    {
        UINT16 Val;
        UINT8 v[2] __PACKED;
        struct __PACKED
        {
            UINT8 LB;
            UINT8 HB;
        } byte;
        struct __PACKED
        {
            __EXTENSION UINT8 b0:1;
            __EXTENSION UINT8 b1:1;
            __EXTENSION UINT8 b2:1;
            __EXTENSION UINT8 b3:1;
            __EXTENSION UINT8 b4:1;
            __EXTENSION UINT8 b5:1;
            __EXTENSION UINT8 b6:1;
            __EXTENSION UINT8 b7:1;
            __EXTENSION UINT8 b8:1;
            __EXTENSION UINT8 b9:1;
            __EXTENSION UINT8 b10:1;
            __EXTENSION UINT8 b11:1;
            __EXTENSION UINT8 b12:1;
            __EXTENSION UINT8 b13:1;
            __EXTENSION UINT8 b14:1;
            __EXTENSION UINT8 b15:1;
        } bits;
    } UINT16_VAL, UINT16_BITS;
    Cet exemple fait partie de "GenericTypeDefs.h qui est fourni par microchip. Comment est transformé un simple = à ton avis ?
    = sur chaque élément de l'union (ce qui serait particulièrement non optimisé) ?
    Ça m'a paru naturel de croire que dans "UINT16_BITS var;", var était un pointeur.

  5. #5
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Chercheur d'emploi
    Inscrit en
    Septembre 2007
    Messages
    7 477
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur d'emploi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 477
    Par défaut
    Citation Envoyé par rms12 Voir le message
    Mais je suis surpris car après avoir déclaré la variable en extern dans un .h, je me retrouve obligé de la redéclarer une nouvelle fois (sans extern) dans un .c (et n'importe lequel en plus) sans quoi j'ai des problèmes de compilation. La déclarer en "static" simplement dans le .h ne fonctionne pas non plus. En comparaison du java, c'est très surprenant et je ne vois pas d'explication.
    C'est ce que je t'explique plus haut : « extern » sert à déclarer la variable sans l'instancier. Tu indiques au compilateur que cette variable existe, mais en dehors du fichier que tu compiles. À ce moment, le compilateur te croit, et résout l'adresse de cette variable à l'édition des liens, après la compilation de tes autres modules.

    Une fois que c'est fait, à ce stade et dans les faits, il faut bien qu'elle soit instanciée quelque part.

    Note que le fonctionnement est exactement le même avec les fonctions : on utilise les prototypes pour les déclarer sans les définir et on met ces prototypes dans le *.h à cet effet. La seule différence est que dans le cas des fonctions, cette manière de faire est implicite parce qu'on instancie pas une fonction : son code est toujours, par nature, en exemplaire unique en mémoire.

  6. #6
    Futur Membre du Club
    Profil pro
    Inscrit en
    Janvier 2012
    Messages
    5
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2012
    Messages : 5
    Par défaut
    Ah ok, je vois, ce que je prenais pour une seconde déclaration avant d'initialiser la variable est en fait l'instanciation.
    Je me retrouve donc avec 3 étapes
    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
    // .h
    // déclaration
    extern int i;
     
    // .c
    // instanciation
    int i;
     
    ...
     
    // au début d'une fonction par ex init()
    void init() {
        // initialisation
        i = 0;
        ...
    }
    Du coup, l'instanciation n'a besoin d'être que dans le .c où se fait l'initialisation. Parfaitement logique donc. J'aurais pensé qu'en ne gardant que le code d'initialisation dans le .c, ça ferait aussi l'instanciation car après tout, rappeler le type "int" est redondant puisque déjà déclaré dans le .h
    Tu admettras que comparé au java, y'a de quoi s'emmêler un peu.

    Merci en tout cas

  7. #7
    Expert confirmé
    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
    Par défaut
    En fait, il y a copie de la zone mémoire occupée par l'objet struct/union dans la zone mémoire assignée à l'autre objet (sauf éventuellement les bytes de padding).
    = sur chaque élément de l'union (ce qui serait particulièrement non optimisé) ?
    et n'aurait pas de sens car dans l'union seul l'un des membres est présent.

    Ça m'a paru naturel de croire que dans "UINT16_BITS var;", var était un pointeur.
    Non, c'est une union.

Discussions similaires

  1. De nouveau perdu avec les graphics
    Par olibara dans le forum C#
    Réponses: 2
    Dernier message: 24/03/2008, 23h21
  2. Perdu avec les vhostS
    Par tonf dans le forum Apache
    Réponses: 12
    Dernier message: 27/11/2007, 17h34
  3. Probleme avec les fichiers .class
    Par Katachana dans le forum Langage
    Réponses: 2
    Dernier message: 13/07/2007, 11h49
  4. [C# ado.NET] perdu avec les datarelations
    Par tatayet_le_felee dans le forum Accès aux données
    Réponses: 9
    Dernier message: 12/06/2007, 14h09
  5. Un peu perdu avec sql server!
    Par jiluc dans le forum MS SQL Server
    Réponses: 2
    Dernier message: 09/12/2005, 13h14

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