Problème avec typedef

Fermé
rbarraud - Modifié le 18 janv. 2022 à 14:21
mamiemando Messages postés 33446 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 20 décembre 2024 - 18 janv. 2022 à 15:01
Bonjour,

J'ai défini un type comme suit :

listSceau.h
typedef struct ElementSceau
{
  char *nombre;
  struct ElementSceau *suivant;
}ElementSceau;

/* Définition d'une Sceaue */
typedef struct Sceau
{
  int taille;
  struct ElementSceau *debut;
  struct ElementSceau *fin;
}*Sceau;

typedef Sceau listSceau[16];


main.c

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "Sceau.h"
#include "listSceau.h"

#define TAILLE_MAX 10
#define TAILLE_NOMBRE 4

int  main(int argc, char *argv[])
{
  listSceau ls;
  Sceau l0 = NULL;


Mais quand je déclare une variable avec ce type dans mon main j'ai l'erreur : unknown type name ‘listSceau’

Pourriez-vous m'aider ?

2 réponses

[Dal] Messages postés 6198 Date d'inscription mercredi 15 septembre 2004 Statut Contributeur Dernière intervention 13 décembre 2024 1 096
Modifié le 6 janv. 2022 à 22:05
Salut rbarraud,

Chez moi ce code compile sans erreurs ni warnings avec
gcc -Wall -Wextra main.c
, un main contenant le code qui suit, et le listSceau.h tel que tu l'as posté :

#include <stdlib.h>
//#include "Sceau.h"
#include "listSceau.h"

#define TAILLE_MAX 10
#define TAILLE_NOMBRE 4

int  main(void)
{

  listSceau ls;
  Sceau l0 = NULL;

  /* do something with ls and l0 */
  (void) ls;
  (void) l0;

  return 0;
}


Comme tu n'as pas fournit Sceau.h son include est commenté dans ce code.

Note qu'il est une bonne pratique de mettre des "header guards" dans les fichiers .h, afin d'éviter les erreurs liées à des inclusions multiples.

Le listSceau.h pourrait dès lors se présenter comme ceci :

#ifndef LISTSCEAU_H
#define LISTSCEAU_H

typedef struct ElementSceau
{
  char *nombre;
  struct ElementSceau *suivant;
}ElementSceau;

/* Définition d'une Sceaue */
typedef struct Sceau
{
  int taille;
  struct ElementSceau *debut;
  struct ElementSceau *fin;
}*Sceau;
  
typedef Sceau listSceau[16];

#endif /* LISTSCEAU_H */


Note quand même que les lignes 10 à 18 ci-dessus sont vraiment horribles :
  • le typedef ligne 11 masque un pointeur vers un type struct qui porte le même nom que l'étiquette de la struct
  • le typedef en ligne 18 n'a aucun sens pour moi ... tu définis listSceau[16] comme un alias de Sceau qui est lui même un pointeur sur struct Sceau....


l'ensemble a pour résultat de tromper l'utilisateur ou le lecteur , et toi même éventuellement... et un constitue un abus de la flexibilité permise par la syntaxe du langage C.

Dal
1
mamiemando Messages postés 33446 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 20 décembre 2024 7 812
Modifié le 18 janv. 2022 à 15:02
Bonjour,

Par rapport à ton code

Au delà de la fonction main incomplète et des fichiers manquants, ton code est étrange. On dirait que tu mélanges l'implémentation d'une liste chaînée et celle d'un tableau.
  • Le nommage est améliorable : ce que tu appelles
    struct Sceau
    devrait s'appeler logiquement
    struct ListeSceau
    ;
  • typedef Sceau listSceau[16];
    n'a pas de sens :
    • soit tu veux déclarer une variable globale nommée listSceau qui fait la taille de 16 instances de type Sceau, et dans ce cas il faut juste écrire
      Sceau listSceau[16
      ];
    • soit tu veux définir un type qui permet de nommer un tableau statique de 16 objets de type
      Sceau
      (ce qui est une mauvaise idée) il aurait fallu écrire
      typedef Sceau[16] listSceau
      .


Plus simplement, tes sceaux sont justes des entiers, peut être que manipuler un tableau d'entiers serait suffisant dans ton cas.

Souvent, en C, on nomme les types plutôt selon le format
ma_structure_t
(le style camel case étant plutôt utilisé en Java et parfois en C++) ce qui donne :

typedef struct _ma_structure_t {
  // ...
} ma_structure_t;


Ici je suis les recommandations de [Dal], qui que "le typedef ligne 11 masque un pointeur vers un type struct qui porte le même nom que l'étiquette de la struct". En fait, ça n'est pas gênant (c'est d'ailleurs un alias fait implicitement en C++) mais c'est vrai que souvent on distingue les deux. Personnellement je n'ai donc aucun complexe à écrire :

typedef struct ma_structure_t {
  // ...
} ma_structure_t;


Liste ou tableaux ?

Il est important de souligner qu'une liste chaînée a ses intérêts, mais force de toute façon a maîtriser des fonctions comme
malloc
et
free
. À partir du moment où tu les maîtrises, tu peux aussi considérer des tableaux alloués dynamiquement. Cela signifie en particulier que tu ne manipules plus que des pointeurs (donc plus besoin de définir des types avec une taille codée en dur).

#include <stdlib.h>
#include <stdio.h>

typedef struct ma_structure_t {
  int attribut1;
  char attribut2;
} ma_structure_t;

int main(){
    unsigned n = 15;
    ma_structure_t * mes_structures = (ma_structure_t *) malloc(n * sizeof(ma_structure_t));
    if (mes_structures) {
        // ...
        free(mes_structures);
    } else {
        fprintf(stderr, "Pas assez de mémoire");
        return 1;
    }
}


Ici on obtient un tableau de 15 éléments de type
ma_structure_t
alloué dynamiquement (ce qui signifie que la valeur de
n
peut être inconnue à la compilation et n'être connue qu'à l'exécution (typiquement parce qu'elle est spécifiée par l'utilisateur).

Alors, liste ou tableau ?
  • Une liste est bien adaptée si tu as fréquemment des éléments à supprimer indépendamment de leur positions, mais si par exemple tu ne supprimes que le premier ou le dernier élément, tu peux faire beaucoup plus simple avec des tableaux. Il est alors judicieux d'envelopper le tableau dans une structure qui indique l'adresse de début et l'adresse de fin et qui déclenche une réallocation en cas de besoin.
  • Pour un tableau, le temps d'accès à un élément se fait en O(1) quelle que soit la taille du tableau.
  • Pour une liste, le temps d'accès à un élément se fait en O(n).


Bref, il est important de bien choisir la structure de donnée adaptée pour avoir un programme plus efficace.

Les listes chaînées

Généralement, on utilise une structure qui modélise un maillon de la liste. Selon que la liste est simplement chaînée ou doublement chaînée, on stocke dans chaque maillon un pointeur vers l'éventuel maillon suivant et (dans le cas d'une liste doublement chaînée) vers le maillon précédent. Ces pointeurs doivent être mis à jour par les primitives qui implémentent la structure de liste chaînée (ajout, suppression d'un maillon).

typedef struct maillon_t {
    //struct maillon_t * precedent;
    struct maillon_t * suivant;
    int donnee;
} maillon_t;


Avec une telle implémentation, on impose que les attributs
suivant
(resp.
precedent
sont initialisés à
NULL
quand il n'y a pas d'élément suivant (resp. précédent).

Chaque maillon stocke en plus soit directement la donnée du maillon (e.g.,
int donnee
), soit un pointeur vers cette donnée. Ainsi, une liste chaînée générique est une liste d'adresse mémoire générique (de type
void *
).

typedef struct maillon_t {
    //struct maillon_t * precedent;
    struct maillon_t * suivant;
    void * donnee;
} maillon_t;


À ce stade, on pourrait tout à fait décréter avec un
typedef
que le type
liste_t
correspond exactement au type
struct maillon_t
.

typedef struct maillon_t liste_t;


... ou à une adresse de maillon (donc
struct maillon_t *
) :

typedef struct maillon_t * liste_t;


Ce choix n'a pas vraiment d'importance, mais ça impactera le reste du code car un pointeur et une structure ne se manipulent pas de la même façon (selon ce choix, pour accéder à un attribut, on utilise
ma_structure.mon_attribut
ou
pointeur_structure->mon_attribut
.

Mais souvent, on définit une structure auxiliaire qui va permettre d'avoir le pointeur sur le premier et le dernier élément. Son principal intérêt est surtout d'accéder en O(1) au dernier élément donc c'est en fait assez anecdotique. Dans la même veine, tu pourrais aussi décider d'y stocker la taille de la liste. L'autre gros intérêt de bien distinguer ces deux structures, c'est qu'on ne risque pas de passer par erreur à une fonction l'adresse d'un maillon quand il faut sémantiquement parlant passer l'adresse d'une liste (le compilateur te le dira, ce qui ne serait pas le cas avec un
typedef struct maillon_t liste_t;
). Bref, la structure peut être définie ainsi :

typedef struct liste_t {
    maillon_t * debut;
    maillon_t * fin;
    unsigned taille;
} liste_t;


Si tu veux voir une implémentation possible de liste chaînée générique, tu peux par exemple regarder ces deux fichiers :

Bonne chance
0