[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