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

Daniel Cordey dc at mjt.ch
Thu Apr 3 14:21:09 CEST 2008


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.

> 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;

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.

> 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.

> 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.

> 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.


> 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" :-)

> 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.

dc



More information about the gull mailing list