[C] pointeur vers structures

Une solution possible est d’avoir une structure qui regroupe les deux :

typedef struct Tableau_t { size_t taille; Element* elements; } Tableau;
Tu peux passer cette structure par valeur sans souci puisque la copie ne fait que copier le pointeur (et non les éléments sous-jacents) :

Tableau foo() { Tableau tmp; tmp.taille = 42; tmp.elements = malloc(tmp.taille * sizeof(Element)); return tmp; } void bar(Tableau t) { for (size_t i = 0; i < t.taille; ++i) { printf(...); // on peut modifier t.elements[i], ça sera visible par l'appelant // car même si la taille et le pointeur sont passés par valeur, // les éléments du tableau sont passés par référence, eux (le pointeur t.elements) } }
Par contre si tu veux une fonction qui modifie la taille et/ou le pointeur lui-même, il faudra passer la structure par référence :

void baz(Tableau* t) { t->taille = t->taille + 1; t->elements = realloc(t->elements, t->taille * sizeof(Element)); }

Ça n’existe pas en C. Un tableau est nécessairement homogène (lire : il ne contient que des éléments de même type, donc de même taille).
Quitte à ce que les éléments du tableau soient des pointeurs vers autre chose, cette “autre chose” pouvant varier d’un élément à l’autre. Mais le tableau lui-même sera toujours un tableau de pointeurs, donc homogène.
Par exemple, un tableau de chaînes ([mono]char*[][/mono]) est bel et bien homogène, il ne contient que des [mono]char*[/mono]. Ce sur quoi pointent ces [mono]char*[/mono], ça ne concerne plus le tableau.

Merci pour ta réponse!

[quote=“syam”]Ça n’existe pas en C. Un tableau est nécessairement homogène (lire : il ne contient que des éléments de même type, donc de même taille).
Quitte à ce que les éléments du tableau soient des pointeurs vers autre chose, cette “autre chose” pouvant varier d’un élément à l’autre. Mais le tableau lui-même sera toujours un tableau de pointeurs, donc homogène.[/quote]
Ca c’est interéssant. Est ce qu’on pourrait avoir quelque chose comme (taille totale)/(taille d’un élément)=nombre d’éléments?

En fait, j’aimerai me passer d’une structure… Si tu regardes mon prototype, il s’agit d’une structure.
Ca n’est pas trop lourd de faire t.contenu.type1 = 1; t.contenur.type2 = “azerty”;?

Dernière question:
Je passais ma structure par référence pour pouvoir modifier son contenu dans la fonction appelée. Mon problème est que je ne recupère pas ma structure une fois de retour dans la fonction appelante! Je me souviens avoir fait des choses comme ça avant… et là, je trouve plus. C’est flippant.
Edit: En fait, c’est un problème de base pointeur/fonction, je reproduis le même problème avec un int tableau[1]

La question étant, si je te donne un pointeur vers un tableau, comment tu détermines sa taille totale ? :wink:

size_t calculer_taille(int* tableau) { // sizeof(tableau) renvoie la taille du pointeur (4 ou 8 selon l'archi de ta machine) // sizeof(*tableau) renvoie la taille du premier élément, donc sizeof(int) // bref, on met quoi ici ? }
Le problème de fond, c’est que tu ne peux pas retrouver la taille d’un bloc mémoire à partir d’un simple pointeur. C’est impossible, le langage ne fournit pas les outils pour. Tu es donc obligé de stocker la taille (ou le nombre d’éléments du tableau) séparément.
Mais comme le nombre d’éléments et le pointeur sont deux informations indissociables, ça a du sens de joindre les deux dans une seule structure : ça permet de les manipuler comme un tout.

Ta formule est bonne (et utilisée fréquemment d’ailleurs), mais elle ne fonctionne qu’avec les tableaux dont la taille peut être déterminée à la compilation :

int tab[42]; size_t nb_elem = sizeof(tab) / sizeof(int); // équivalent : sizeof(tab) / sizeof(*tab)

Non, quasiment tous les processeurs (sauf peut-être certains microcontrolleurs, mais là ça devient très spécifique) ont un mode d’adressage qui intègre le déplacement directement dans les instructions.
Autrement dit, [mono]a.b.c = 42[/mono] est une seule instruction. L’adresse de base est [mono]a[/mono], et le déplacement [mono].b.c[/mono] est connu au moment de la compilation (c’est la position de [mono]b[/mono] dans [mono]a[/mono] plus la position de [mono]c[/mono] dans [mono]b[/mono]). Tu peux rajouter autant de niveaux que tu veux, le déplacement sera toujours calculable à la compilation et l’adressage reviendra toujours à [mono]base + déplacement[/mono], chose qui ne coûte rien une fois traduite en assembleur.

Là où il y a un (tout petit) coût c’est quand tu fais ça avec des pointeurs : [mono]a->b->c[/mono] doit d’abord lire le contenu de [mono]a[/mono], puis de [mono]a + (déplacement de b dans *a)[/mono] pour trouver l’adresse de base, à laquelle il faut joindre le déplacement de [mono]c[/mono] dans [mono]*b[/mono] pour pouvoir accéder au contenu de [mono]c[/mono]. En résumé, ça fait deux lectures supplémentaires avant de pouvoir accéder à [mono]c[/mono] (n’oublie pas que les déplacements sont connus à la compilation, et qu’un accès [mono]base + déplacement[/mono] est une seule instruction).

Ceci étant dit, on est dans le domaine de la micro-optimisation là, ça n’a aucune importance en réalité (sauf quand ton profileur te dit qu’il y a un problème). Ce qui a de l’importance, c’est ton algorithme lui-même : tu pourras micro-optimiser tout ce que tu veux, si ton algo est en O(n²) il restera lent. Modifie-le en O(n) et là tu as des vrais gains !

J’avoue, je comprends pas trop le problème. Si on utilise le code dans mon message précédent :

Tableau t = foo(); // t.taille == 42 baz(&t); // t.taille == 43 baz(&t); // t.taille == 44
C’est justement un des intérêts de passer par référence : les modifications sont directement visibles par l’appelant.

En bonus : un truc vachement utile pour faire le tri dans ta tête quand tu as du mal, c’est de faire des dessins sur papier.

En l’occurrence, imagine que ta feuille représente la mémoire de ton programme.
Tu fais des cases pour chaque variable. Quand c’est un pointeur tu fais une flèche depuis ta case vers ce sur quoi tu pointes (une autre case). Quand c’est une structure, tu fais des sous-cases pour ses champs.
Quand tu passes par valeur, tu dessines une copie identique de ta variable, tout en conservant la destination originale de tes éventuels pointeurs (autrement dit, tu as deux cases différentes qui pointent vers la même chose).
Quand tu passes par référence, tu dessines une nouvelle case pointeur qui pointe vers ta variable d’origine.

Ça te permettra de voir facilement ce qui change ou pas, les données “partagées” entre appelant et appelé via des références par forcément évidentes au premier abord, etc.
Essaye déjà avec les deux fonctions [mono]foo() / baz()[/mono] ainsi que leur exemple d’utilisation dans mon message précédent, c’est suffisamment simple pour que ça ne te prenne pas de temps et tu verras ce que ça donne.

Ça paraît tout con comme truc, mais ça aide beaucoup quand les choses deviennent trop compliquées. Au fil du temps et avec l’expérience tu t’habitueras à le faire de tête, puis ça viendra automatiquement sans y penser, mais je te garantis que tu continueras à griffonner des trucs complexes que tu as du mal à comprendre (plus tellement l’organisation mémoire, ça sera d’autres problèmes, mais le principe reste le même). :wink:

On pourrait croire qu’un programmeur n’a besoin que d’un ordinateur mais c’est faux : on utilise des tonnes de papier pour pouvoir s’en sortir. :mrgreen:

Merci encore syam.
Je vais me résoudre à utiliser une structure pour garder la taille du tableau.

Juste pour t’embeter: les chaines de caractères n’ont pas besoin d’être accompagnées d’un entier qui spécifie leur taille. Si j’ai bien compris, une chaine de caractères se termine forcèment par “\0”. Donc on peut retrouver sa taille simplement à partir du pointeur non?
Est ce que c’est raisonnable de faire pareil avec un tableau (d’entier par exemple?): faire pointer la dernière case sur NULL ou autre chose qui permette de retrouver sa taille.

Généralement, je m’en sors sans shéma. Mes projets sont petits et ma limitation vient plutôt de ma compréhension du C en général.
Par rapport à ma question (seg fault&co), je me suis en partie répondu à grd renfort de printf. Je développe:

[code]void foo (int* tab) {
tab = (int*)realloc(tab,1*sizeof(int));
//printf tab != 0
tab[0] = rand();}

int main () {
int *tab = NULL;

//printf tab = 0
foo(tab);
//printf tab = 0
//tout accés à *tab -> seg fault.
return (0);

}[/code]
Ca plante. En affichant tab à différents endroits, je remarque que tab = 0 au début du main puis prend un autre valeur après le realloc.
De retour dans le main, printf("%d\n",tab) = 0. Le travail fait dans foo n’est plus accessible. Il faut donc que je fasse la première allocation dans le main (comme suggéré par pascalhambourg) ou bien que la fonction qui fait l’allocation renvoie le pointeur correspondant.

Voila, comment je me représente les pointeurs:
&tab = adresse du pointeur, l’endroit où est stocké le pointeur.
tab = valeur entière: l’adresse de la cible.
*tab = l’objet pointé: entier, structure, etc…

Le pointeur est bien déclaré dans le main. Pour moi, 4 ou 8 bytes lui sont alloués en attente de recevoir une adresse vers quoi pointer. Après l’allocation, c’est comme si le pointeur n’était plus le même… Je pensais que realloc se contenterait d’attacher un espace mémoire mais j’ai l’impression que le pointeur lui même change d’adresse. Pourtant realloc est sensé se servir de l’adresse qu’on lui fournit en argument.
Pour les réallocations suivantes, pas de soucis.

Ça n’existe pas en C. Un tableau est nécessairement homogène (lire : il ne contient que des éléments de même type, donc de même taille).
Quitte à ce que les éléments du tableau soient des pointeurs vers autre chose, cette “autre chose” pouvant varier d’un élément à l’autre. Mais le tableau lui-même sera toujours un tableau de pointeurs, donc homogène.
Par exemple, un tableau de chaînes ([mono]char*[][/mono]) est bel et bien homogène, il ne contient que des [mono]char*[/mono]. Ce sur quoi pointent ces [mono]char*[/mono], ça ne concerne plus le tableau.[/quote]
C’est comme ça qu’on l’apprend, puis quand on comprend comment fonctionne le typage en C, on se dit que c’est pas si vrai que ça. Il suffit de respecter les alignements (sachant que c’est aussi important quand tu fait des [mono]struct[/mono]) et de jouer avec des [mono]cast[/mono] et l’arithmétique de pointeurs pour faire ce que tu veux.

Et tu te retrouves avec de l’Undefined Behaviour à tous les coins de rue, sans aucune garantie que ton code fonctionne à l’identique sur un autre compilateur (y compris d’autres versions de ton propre compilateur). :unamused: Même si l’UB est un peu moins un problème en C qu’en C++, ça reste une des pires choses qu’un programmeur puisse perpétrer.
D’autant qu’il y a d’autres moyens d’arriver à la même chose en respectant le standard et donc en respectant le modèle homogène (par exemple les unions qui t’évitent de faire des casts UB, sous réserve de les utiliser correctement).

Et tu te retrouves avec de l’Undefined Behaviour à tous les coins de rue, sans aucune garantie que ton code fonctionne à l’identique sur un autre compilateur (y compris d’autres versions de ton propre compilateur). :unamused: [/quote]
Je ne suis pas un spécialiste des UBs comme tu dis. Je parle d’un truc extrêmement moche plus pour la réflexion qu’autre chose, mais je ne vois pas en quoi il peut y avoir des UB. Pas mal de code C fait de l’allocation et du cast à la main (des fois par nécessité). Tu met ce que tu veux derrière un pointeur tant que l’espace alloué derrière est suffisamment grand. On trouve souvent l’exemple d’un pointeur vers un entier qui est casté en tableau de caractère.

Encore une fois je ne dis pas que c’est propre et encore moins qu’il faut le faire (mon premier message n’était probablement pas assez clair sur mon intention), mais je me pose des questions sur le ou les UBs dont tu parle.

En fait il y a très peu de casts de pointeurs qui ne sont pas UB, c’est vraiment un terrain miné (les casts de valeurs c’est une autre histoire car on convertit la valeur au lieu de la réinterpréter, c’est une sémantique de copie et non de référence). Il y a deux grandes catégories qui sont bien définies par le standard :
[ul][li] les casts depuis ou vers [mono]void*[/mono], à condition que la chaîne complète des casts ne soit pas UB (ex. [mono]int* -> void* -> int*[/mono] est OK mais [mono]int* -> void* -> float*[/mono] est UB, car [mono]int* -> float*[/mono] est UB en premier lieu)[/li]
[li] les casts pour simuler l’héritage dans les structures (puisqu’il n’y a pas d’héritage en C, on place la base comme premier champ de la structure dérivée, et on peut caster de l’une à l’autre, sous réserve que la valeur soit effectivement une instance de la structure dérivée)[/li][/ul]

Une chose est sûre : un cast entre pointeurs vers des types sans rapport est UB. Comme déjà dit, un cast d’un [mono]int*[/mono] vers un [mono]float*[/mono] est incorrect.

Là où ça devient vicieux, c’est que l’UB peut très bien donner l’impression de fonctionner correctement, surtout les choses aussi bas niveau que les casts de pointeurs… jusqu’à ce que ça t’explose à la gueule parce que tu as changé de (version de) compilateur ou même simplement que tu as changé tes options d’optimisation.
Faut pas oublier qu’un compilateur fait des analyses très poussées sur l’aliasing des valeurs (les différents “chemins” pour accéder à une zone mémoire particulière) et que les casts de pointeurs ont une furieuse tendance à fausser ces analyses, et donc le code généré.

Le truc c’est que je suis plus habitué à penser en C++, où le système de typage a été durci justement à cause de ces problématiques, et où on nous fournit des outils adaptés pour les gérer : chaque [mono]*_cast[/mono] ([mono]static_cast / reinterpret_cast / dynamic_cast / const_cast[/mono]) a une sémantique bien précise que le compilateur peut vérifier, alors qu’en C tout est regroupé dans une seule syntaxe fourre-tout monovaleur[/mono] qui rend difficile la distinction des divers cas.
Et bien entendu le problème avec cette syntaxe unique en C c’est que tu peux très bien vouloir un [mono]static_cast[/mono] mais si celui-ci n’est pas possible le compilateur va silencieusement te faire un [mono]reinterpret_cast[/mono] à la place (UB dans la plupart des cas).

Pour revenir à l’exemple [mono]int* -> float*[/mono], en C++ si tu essayes de faire [mono]int* x; float* y = static_cast<float*>(x);[/mono] le compilateur va te hurler dessus, avec raison. La seule manière pour t’en sortir est d’utiliser un [mono]reinterpret_cast[/mono] à la place (ou un cast C qui va au final être se comporter en [mono]reinterpret_cast[/mono]) ce qui est l’équivalent d’un gros panneau clignotant rouge : UB UB UB UB.
En C c’est tout aussi incorrect, mais le compilateur ne te le dit pas… :open_mouth:

[quote=“Funkygoby”]les chaines de caractères n’ont pas besoin d’être accompagnées d’un entier qui spécifie leur taille. Si j’ai bien compris, une chaine de caractères se termine forcèment par “\0”. Donc on peut retrouver sa taille simplement à partir du pointeur non?
Est ce que c’est raisonnable de faire pareil avec un tableau (d’entier par exemple?): faire pointer la dernière case sur NULL ou autre chose qui permette de retrouver sa taille.[/quote]
Oui tu peux tout à fait faire comme ça, à condition bien sûr que cette valeur “de garde” ne soit pas utilisée dans tes données “normales”.
Par contre, ça veut dire qu’à chaque fois que tu as besoin de connaître la longueur, tu dois te farcir tout le tableau en O(n)… Ça peut poser des problèmes de performances selon les cas (même pour les chaînes de caractères, hein, si tu fais des [mono]strlen[/mono] répétés tu as tout intérêt à cacher le résultat dans une variable).

Oui, et c’est normal vu ton code. Dans [mono]foo()[/mono] tu ne modifies pas le [mono]tab[/mono] présent dans [mono]main()[/mono] mais la copie du [mono]tab/main()[/mono] que tu as passée à [mono]foo()[/mono]. Cette copie est distincte de l’original, qui n’est pas mis à jour en même temps que la copie.

Ce qu’il faut faire, c’est modifier l’original donc le passer par référence. Passer par référence = ajouter un niveau d’indirection (autrement dit, utiliser un pointeur vers ton original).
Pour une valeur simple, c’est clair :

void foo(int* y) { *y = 42; } int main() { int x = 0; foo(&x); // x == 42 return 0; }
Tu noteras l’opérateur [mono]&[/mono] pour passer l’adresse de [mono]x[/mono] à [mono]foo()[/mono], en C c’est la seule façon d’obtenir une référence vers une variable. Tu noteras aussi que tu ne l’utilises pas dans ton code problématique.

En généralisant ça (on remplace simplement [mono]int[/mono] par un [mono]TYPE[/mono] générique qui sert de “bouche trou” en attendant), on se retrouve avec un modèle du type :

void foo(TYPE* y) { *y = valeur; } int main() { TYPE x = valeur; foo(&x); return 0; }
Maintenant qu’on a un modèle clair, tu peux l’appliquer à n’importe quel type simplement en remplaçant [mono]TYPE[/mono] par ton type réel. Pour [mono]int*[/mono] :

void foo(int** y) { // ^^^^^ c'est un pointeur vers un int* *y = valeur; // on va voir après } int main() { int* x = NULL; foo(&x); return 0; }
Si tu as suivi jusqu’ici, impeccable, il ne reste plus qu’à savoir ce qu’on va mettre dans [mono]foo()[/mono]. Ça va être quasiment identique à ce que tu avais déjà fait, sauf qu’au lieu d’avoir [mono]tab[/mono] (un [mono]int*[/mono]) on a [mono]y[/mono] (un [mono]int**[/mono]). Pour obtenir un [mono]int*[/mono] à partir d’un [mono]int**[/mono] il faut le déréférencer : [mono]y[/mono] est donc un [mono]int[/mono]. Bref, il suffit de remplacer [mono]tab[/mono] par [mono]*y[/mono] dans ton code pour [mono]foo()[/mono], on obtient :

void foo(int** y) { *y = (int*)realloc(*y, 1 * sizeof(int)); (*y)[0] = rand(); } int main() { int* x = NULL; foo(&x); return 0; }
Si les noms de variables [mono]x[/mono] et [mono]y[/mono] te chiffonnent (et tu aurais raison, ils ne veulent rien dire donc c’est un très mauvais choix), tu peux les remplacer respectivement par [mono]tab[/mono] et [mono]ptab[/mono]. Habituellement j’aime pas trop la notation hongroise (rajouter un préfixe devant le nom pour indiquer son type, ici [mono]p[/mono] pour pointeur) mais y’a certains cas où c’est quand même utile pour pas se mélanger les pinceaux :

void foo(int** ptab) { *ptab = (int*)realloc(*ptab, 1 * sizeof(int)); (*ptab)[0] = rand(); } int main() { int* tab = NULL; foo(&tab); return 0; }

Merci pour l’explication sur les [mono]cast[/mono], je me rend compte que j’ai quelques lacunes en C (je n’ai presque jamais fais que du C++ à la place).

Bah tu sais, la majeure partie de ce que je connais en C, ça vient du sous-ensemble C du C++. :wink:
Il y a quelques différences mais c’est des cas très spécifiques, quasiment toutes les notions sont communes aux deux. Le truc c’est qu’en C++ on a énormément plus d’outils que juste le sous-ensemble C, du coup on l’utilise très peu donc on le connaît assez mal généralement).
Après c’est vrai aussi que j’ai bossé en C très récemment donc j’ai eu l’occasion de me remettre à niveau (c’est un langage… euh… très rudimentaire au final :unamused: moi qui écris beaucoup de code générique j’étais malheureux sans mes templates, alors que l’OOP bizarrement ça m’a pas manqué :mrgreen:).

Bah tu sais, la majeure partie de ce que je connais en C, ça vient du sous-ensemble C du C++. :wink:
Il y a quelques différences mais c’est des cas très spécifiques, quasiment toutes les notions sont communes aux deux. Le truc c’est qu’en C++ on a énormément plus d’outils que juste le sous-ensemble C, du coup on l’utilise très peu donc on le connaît assez mal généralement).[/quote]
C’est ça et l’écart se creuse avec C++11 (par écart je ne parle pas de qualité, mais d’écart entre les manière de programmer).

C’est dommage l’orienté objet est un vrai plus, il permet de se servir du typage facilement (là où l’inférence de type est largement plus puissante et bien plus complexe à prendre en main).

T’inquiète je fais beaucoup d’OO en temps normal, ce que je voulais dire c’est qu’on s’en passe très bien quand il n’y a pas le choix.
Par opposition à l’absence de templates qui rend les choses beaucoup plus difficiles… :wink:

C’est simple!
Merci Syam!

Je pensais que, comme un tableau (déclaré [mono]TYPE *t[/mono]) était un pointeur. Le passer par “valeur” revenait à passer son contenu par “référence”.
En fait il faut penser: Je veux modifier ma variable, je la passe par référence peu importe le type de ma variable. on appelle la fonction avec [mono]foo(&variable)[/mono] et le prototype de foo sera [mono]foo(*variable)[/mono].
C’etait évident pour moi avec des variables “simples” mais avec des tableaux, je devenais fou.

J’ai passé des jours à me rendre fou sur un autre truc. La déclaration de plusieurs pointeurs.
Je pensais que [mono]char* chaine1,chaine2,chaine3;[/mono] était équivalent à [mono]char *chaine1,*chaine2,chaine3;[/mono]. Quand on se souvient que [mono]TYPE v;[/mono] vaut [mono]TYPE *v;[/mono], on se sent con…
J’ai chassé le seg fault sur mes chaines de caractères pendant un moment.

J’espère pouvoir bientôt balancer les qq pauvres lignes de codes et recueillir vos avis.

C’est bel et bien le cas, mais ce n’est que le contenu qui est passé par référence, pas le pointeur vers le tableau. :wink: Mais tu avais compris.

[quote=“Funkygoby”]J’ai passé des jours à me rendre fou sur un autre truc. La déclaration de plusieurs pointeurs.
Je pensais que [mono]char* chaine1,chaine2,chaine3;[/mono] était équivalent à [mono]char *chaine1,*chaine2,chaine3;[/mono]. Quand on se souvient que [mono]TYPE v;[/mono] vaut [mono]TYPE *v;[/mono], on se sent con…[/quote]
Oui ça fait partie des pièges du C(++).
La solution, c’est de ne JAMAIS déclarer plusieurs variables sur une même ligne. C’est un style qui est très souvent obligatoire dans plein de projets car ça permet justement d’éviter ces erreurs. Et puis faire plusieurs déclarations séparées ne change rien au programme une fois compilé, donc pas d’hésitation.

Les déclarations multiples comme ça c’est encore plus un problème en C++ où on déclare généralement [mono]TYPE* var;[/mono] plutôt que [mono]TYPE var;[/mono] comme en C (l’idée étant : [mono]var[/mono] est de type [mono]TYPE[/mono] plutôt que [mono]var[/mono] est de type [mono]TYPE[/mono]).
Perso je suis plus habitué au C++ donc [mono]TYPE
var;[/mono] me paraît plus logique (le pointeur fait partie du type) mais bon on pourrait en discuter des années il y aurait toujours quelqu’un pour ne pas être d’accord. :laughing:

Voila! Une première partie de terminée: saisie/edition/sauvegarde.
L’interface est pourrie et je pense que je ne gère pas bien la mémoire.
Le main() est gros et pas très lisible à cause de ma manière de faire l’interface utilisateur.

En tout cas, je pense m’en être sorti avec les chaînes de caractère (voir la fonction getstring()).
Les conseils de syam ont été vitaux. Exemple: fonctions read_file et write_file.
Plus de segfault à priori même en cherchant à le faire planter.
Prochaine étape: écrire les fonctions qui vont faire mes calculs à ma place.
Changer l’interface pour la faire ressembler à celle de fdisk par exemple.

Vos avis sont les bienvenus!
decla.tar.gz (5.41 KB)