[gull] Subtilité de C n° 2 : type de retour de malloc

Leopoldo Ghielmetti leopoldo.ghielmetti at a3.epfl.ch
Thu Apr 3 15:45:28 CEST 2008


On Thu, 2008-04-03 at 14:21 +0200, Daniel Cordey wrote:
> On Wednesday 02 April 2008, Leopoldo Ghielmetti wrote:
> 
> > Oui, et je dis exactement la même chose. La dessus on est d'accord à
> > 100%, mais il faut que tu relise les bouts de code en question.
> > Il s'agit de la différence entre:
> > int *p;
> > p = (int*)malloc(sizeof(int));
> >
> > et
> > int *p;
> > p = (int*)malloc(sizeof(*p));
> >
> > Ce qui est EXACTEMENT equivalent car *p est un int. Il n'a jamais été
> > question d'une conversion entre un pointeur et un int.
> 
> Oh ouiiiiiiiiiiiii... je n'utilise jamais cette notation dans un sizeof... et 
> je l'ai faussement/implicitement associee a (int *)... Comme il me semble 
> logique d'utiliser un 'type' de base ou une variable dans sizeof, je n'ai 
> jamais envisage d'y rajouter un '*'. Je trouve... tres ambigu car ca [pourait 
> donner la fausse impression que l'on pourait determiner la taille de l'objet 
> pointe par... mais bon... c'est syntaxiquement correcte. Je vous prie 
> d'excuser ma confusion. Bien que ca ne me plaise toujours pas (mais ca c'est 
> perso), c'est juste :-)
> 
> > Par contre je faisait référence à l'allocation d'un pointeur ou d'une
> > structure, c'est à dire:
> >
> > struct toto *t;
> > t = (struct toto*)malloc(sizeof(struct toto)); // dangereux
> >
> > et
> > struct toto *t;
> > t= (struct toto*)malloc(sizeof(*t));
> 
> Je n'ai jamais utilise la deuxieme notation pour les raisons que j'ai evoque. 
> Mais je definis toutes mes structures a l'aide de 'typedef'  et les noms de 
> celles-ci dans les sizeof. Je n'ai donc jamais eu de probleme, bien qu'ayant 
> ecrit pas mal de code de claculs de pointeurs... Il est aussi vrais que je 
> n'ai jamais confronte mes codes a des compilateurs 'permissifs' et pas tres  
> fiables.

Oui, c'est étrange et déroutant. Quand j'avais vu ça j'ai été surpris.
Mais actuellement je fais attention.
Ce ne serait pas le premier bug de compilateur auquel je suis confronté
et ce ne sera sûrement pas le dernier.

> > Pour la structure c'est dangereux car on risque des problèmes de
> > padding.
> 
> Pourquoi vouloir absolument utiliser des '*' dans les sizeof ? EN ecrivant :
> 
> typedef struct
> {
> 	int a;
> 	double b;
> } toto;
> 
> typedef toto *x;
  ^^^^^^^ je supposes que c'est un oubli. :-)
> x = (toto *)(malloc(sizeof(toto))
> 
> Je n'ai pas de probleme. Bien que les typdefs ne soient pas necessaire, ils 
> decrivent clairement ce qui est ecrit. Je definis des type et "j'instancie" 
> ensuite. Je peux donc facilement utilise le type "toto" partout ou je veux et 
> sans ambiguite aucune.

Oui. En principe je suis parfaitement d'accord. Mais ce bug que j'avais
eu ça me fais toujours un peu peur. C'est le spectre du bug qui revient.
Donc je préfère utiliser la même variable dans le malloc, mais
normalement les deux notations sont équivalentes (ou elles devraient
l'être).

Une chose que je ne fais jamais (mais qui est un peu O.T.) c'est de
définir des typedef sur des pointeurs. Je préfère laisser les *
explicites partout. Un typedef sur un type * cache le pointeur et après
on se perd.

Beaucoup d'erreurs que j'ai vu (entre autre des conversions mal placées)
étaient dues a un compilateur permissif associé à des typedef sur des
types pointeurs.
La plupart des programmeurs nommes ces types pQuelquechose pour indiquer
que c'est un pointeur sur Quelquechose, moi je préfère utiliser
*Quelquechose, j'évite un p et j'ajoute un *, en bytes c'est pareil et
en compréhension c'est (à mon avis) mieux.

> > Note qu'il serait par contre correct d'écrire:
> > p = (int**)malloc(sizeof(int*));
> > car dans ce cas le sizeof serait calculé correctement.
> 
> Exactement ce que j'ai toujours fait. Je pretend que cette discipline evite 
> peut-etre des erreurs...
> 
> > Pour éviter tout cela je préfère caster le malloc et utiliser la
> > variable pour le sizeof.
> 
> Ben voila... :-)
> 
> > Je ne me rappelles plus ou je l'ai appris peut être même au poly à
> > l'époque de mes études, en tout cas sur google j'ai trouvé ceci
> > http://home.att.net/~jackklein/c/inttypes.html#int
> 
> Bon, l'article est intitule : Integer Types In C and C++
> 
> Or, le langage C++ est tres largement posterieure a C et la redaction de cet 
> article a donc ete redige... bien apres les raisons qui ont pousse a avoir 
> des types short, int et long en C ! Il s'agit donc d'un travail d'explication 
> entre ce qu'il faut comprendre en C et C++.
> 
> Il se trouve que le langage C a ete develope par K&R, dans le but d'avoir un 
> langage facilement portable entre differentes architecture.. 16bits a 
> l'epoque !!! Il faut donc tenir compte du fait que le nombre de bytes 
> utilises pour short, int et long varie; et il est imperatif d'en tenir compte 
> en ecrivant du code approprie.
> 
> 
> > > Il me semble qu'il existe une sorte de convention qui dit que sur une
> > > machine 16 bits, short, int et long ont la meme taille (2 bytes). Sur une
> > > machine 32 bits short -> 2 bytes, int et long 4 bytes. Sur une machine 64
> > > bits : short -> 2 bytes, int -> 4 bytes et long -> 8 bytes.
> >
> > Probablement. En tout cas je trouve que c'est très mal défini. Ils
> > auraient du spécifier la taille de ces objets depuis le début. Ça aurait
> > évité un tas de confusions. Mais ils ne l'ont pas fait justement parce
> > qu'à l'origine le int était le type "naturel" du processeur, donc ça
> > dépendait de la machine.
> 
> Le langage C n'a pas ete developpe comme un "produit" commercial, mais plutot 
> qu'un un outil interne aux AT&T labs. Il n'y avait donc pas de confusion 
> puisque les differents compilateurs etaient developpes par la meme equipe. Il 
> etait destine a compile un kernel ecrit dans ce langage, kernel qui devait 
> permettre d'avoir le meme OS et les memes outils sur toutes les machines 
> utilisees par AT&T (PDP-10/11, VAX, etc.). Comme il y avait de (tres) grandes 
> disparites dans les architecture, processeurs (modes d'adressages), le 
> langage se devait d'offrir une grande liberte pour pouvoir ecrire du code 
> essentiellement pour un kernel et des divers (forte interractions avec les 
> bus et cartes E/S). Cette liberte a ouvert la voie a de multiples 
> interpretations et ambiguites. J'aurais tendance a dire que les options de 
> gcc sont la source d'information la plus precise en ce qui concerne les 
> tailles des type short, int et long.

Eh oui, malheureusement on paye l'histoire de la chose, comme souvent en
informatique.

> > Sémantiquement les trois écritures représentent trois objets différents.
> > Mais ceci est laissé aux bonnes pratiques des programmeurs, le
> > compilateur ne s'intéresse qu'à l'implémentation.
> 
> Oui... mais pourquoi melanger ces usages dans le meme code ? Il n'y a aucune 
> necessite, si ce n'est de rendre le code moins lisible.

Oui, mais ceci est valable dès qu'il y a un seul programmeur qui à
travaillé sur le code. Quand il y a des dizaines de personnes qui y ont
touché, il faut s'attendre que chacun y a apporté son grain de sel.
Je ne peux pas leur en vouloir plus que tant, vu que moi aussi je fais
la même chose (mais la différence c'est que ma notation est la
meilleure, donc je suis excusé ;-) ).
Après c'est une question de normes, mais parfois ces normes sont
ambiguës ou définissent des notations qui peuvent conduire à des
erreurs, donc elles n'améliorent pas forcement la lisibilité du code.
Elles ont par contre l'avantage d'uniformiser la notation.

> > Je suis d'accord d'utiliser ta méthode des macros si le calcul doit se
> > faire dans une façon bien précise et maîtrisée par le programmeur mais
> > il s'agit d'un cas particulier. Moi je préfère bien instruire le
> > compilateur de façon qu'il puisse faire son travail au mieux et dans le
> > cas de la taille d'une structure ou une variable je me vois mal faire
> > mieux que lui en utilisant des macros.
> 
> La macros concernee est destinee a calculer la taille d'uns structure en 
> incluant le 'tail padding' en fonction de criteres fixes par le programmeur a 
> la compilation. Ainsi, je peux decider d'aligner mes codes sur 2^N bytes.

Je comprends, ça sa peut être utile.

> > Python est un langage joli, mais malheureusement les tentatives que j'ai
> > faites pour l'utiliser ne sont pas toutes allées à bon port. Vu qu'il
> > vérifie les types à l'exécution et non à la "compilation" il m'est
> > souvent arrivé d'avoir un code qui tourne très bien dans le 99% des cas
> > et puis tout à coup voilà une belle exception car je me retrouve un
> > objet non attendu. Inutile de dire qu'il est impossible de savoir qui a
> > initialisé la variable en question car toutes les opérations précédentes
> > étaient parfaitement légales.
> 
> Il m'arrive souvent de creer un super-class qui defini les methodes par 
> defaut. Lors de l'heritage, les fonctions sont normalement "surchargees". EN 
> cas d'oubli ou d'objet incomplet, la methode par defaut s'executera et 
> j'evite ainsi la fameuse "exception". La methode par defaut peut tres bien 
> m'afficher le nom de la classe manipulee ainsi que le module, etc. Ca 
> m'arrive souvent d'utiliser cette methodologie pendant le developement, alors 
> que les codes ne sont pas complets. Je considere que toute "exception" est un 
> bug de ma part et je  m'interdis d'utilser des "try/except". Au pire, je veux 
> bien faire un "if isinstance(...)" s'il n'y a vraiment aucun autre moyen; 
> mais je m'apercois que j'arrive aussi a eliminer pratiquement tous 
> les "isinstance" :-)

Oui, mais comment tu fais pour éviter de mettre un mauvais objet dans
une liste qui n'est pas forcement prévue pour l'accueillir? Il faudrait
remplir le code de isinstance avant toute opération sur les listes.
Mais ça ne risque pas de trop alourdir le code?

Sinon il faut commencer à écrire le code en ajoutant des isinstance
partout pour ensuite refaire une passe et les effacer une fois le code
débuggé. Il ne me semble pas qu'il y a des macros en Python, donc on ne
peut pas juste avoir un #define DEBUG on/off qui va bien.

> > Donc la je préfère nettement le langage qui me plante tout de suite au
> > nez dès qu'il y a une affectation sur un type non correct.
> 
> Tres juste, mais j'essaie d'eviter ces problemes en concevant mes codes de 
> maniere differentes en Python.

C'est justement le problème. Tant qu'on n'a pas assimilé le style de
programmation Python on risque des erreurs de débutant. Malheureusement
je suis encore débutant en Python et je n'ai pas beaucoup de
possibilités d'expérimentations car au boulot on ne l'utilise pas. 

> dc

ciao, Leo




More information about the gull mailing list