[gull] Subtilité de C n° 2 : type de retour de malloc
Daniel Cordey
dc at mjt.ch
Wed Apr 2 18:16:35 CEST 2008
On Wednesday 02 April 2008, Leopoldo Ghielmetti wrote:
> Pas totalement, le malloc(sizeof(int)) alloue un int et malloc(sizeof
> *p) aussi (si p est un int*) la difference vient du moment ou tu alloue
> un pointeur (comme tu le fais remarquer plus loin) ou une structure.
Non, ll est tres important d'arreter a considerer que c'est equivalent.
Ca "peut" etre equivalent sur certaines architectures, avec un certain OS et
certians compilateurs. Mais ce n'est ABSOLUMENT PAS GARANTI ! Un pointeur est
un pointeur et un int est un int ! Que je puisse mettre la valeur d'un
pointeur dans un int est possible mais pas une bonne pratique. EN reservant
une "espace" memoire pour y stocker une adresse (pointeur), il est plus
logique de dire je reserves une taille correspondant au stockage d'un
pointeur de type X. plutot que "je reserve un espace pour y stocker un
entier". Cela introduit une confusion sur laquelle se rue les pareseux et
ceux qui ne maitrisent pas bien la notion de pointeurs. C'est le jours ou
l'on manipule des pointeurs a 4 ou 5 niveaux que l'on commence a bien
comprendre les dangers de l'amalgame.
> Pas vrai car si j'ai bien lu la documentation du C le type int est le
> type natif de la machine (ou à défaut celui qui s'exécute le plus
> rapidement) c'est à dire que sur une machine à 16b le int est à 16b, sur
> une machine à 32b le int est à 32b et sur une machine à 64b il est à
> 64b.
Ah, ca c'est nouveau ! EN fait, ANSI ne decrit absolument pas le nombre de
bytes utilises pour stocker les short, int, lon et long long... Cela est
explicitement laisse au compilateur, en liaison avec le CPU et l'OS.
La seule chose qui soit precisee est que :
sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long
long)
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.
l'option =m64 de gcc di ceci :
Generate code for a 32-bit or 64-bit environment. The 32-bit environment sets
int, long and pointer to 32 bits and generates code that runs on any i386
system. The 64-bit environment sets int to 32 bits and long and pointer to 64
bits and generates code for AMD's x86-64 architecture.
Le manuel de gcc dit aussi ceci a propos des options pour un processeur
Athlon :
The default size of ints, longs and pointers depends on the ABI. All the
supported ABIs use 32-bit ints. The n64 ABI uses 64-bit longs, as does the
64-bit EABI; the others use 32-bit longs. Pointers are the same size as
longs, or the same size as integer registers, whichever is smaller.
Il faut donc bien faire attention aux options du compilateur ainsi que la
version de l'OS et le type de processeurs pour lequel on compile !
> Et ce selon les spécifications du C (sauf si les spécifications
> récentes ont changé cela).
Il n'y a pas de specification du C a ce sujet.
> j'imagine que c'est de même pour tous les portages 32b->64b.
> Cette définition de int fait en sorte que sur la plupart des machines
> 32b le int est égal au long (car ils font les deux 32b) et c'est une des
> raisons de la mauvaise programmation qui s'est instaurée cette dernière
> décennie.
Oui, et surtout parceque cette ambiguite a ete petpetuee par beaucoup de
gens. Il serait sans doute plus juste de dire que l'on peut considerer les
pointeurs comme des 'unsigned long', plustot que comme des 'int'. Le fait de
ne pas preciser 'unsigned' peut avoir des consequences catastrophiques car il
peut y avoir 'masquage' du bit de signe lors d'operation arithmetiques.
> Par contre ce sera vrai sur les machines qui ont les registres qui
> stockent les pointeur différents en taille de ceux qui stockent un
> entier.
Typyquement 64 bits...
> Que dit le manuel du C en propos?
Lequel ? ANSI C ou K&R ? Oublions K&R qui a ete justement suplante par ANSI
pour ces raisons d'ambiguite... il y a deja 20 ans.
> A ma connaissance tous les compilateurs considères les deux comme des
> notations alternatives.
> En effet:
> int a[];
> et
> int *a;
> sont compilé de la même façon partout (à ma connaissance). Et avec un
> langage comme le C je vois mal faire autrement.
Le pobleme vient a partir du deuxieme niveau de pointeur, pas du premier comme
decrit ici. Dans ce cas, on ne peut pas considerer a[][] comme **a ou *a[].
Le probleme reside lors de l'allocation et de l'acces. On peut bien sur tout
simuler en C avec N mallocs. Ce n'est pas parceque l'on est en mesure de
construire un tableau a deux dimensions a l'aide de **a et de malloc que l'on
peut considerer que c'est equivalent. Pour un type donne, il est important de
lire la syntaxe pour ce qu'elle decrit. A savoir :
int a[][]; /* tableau d'entier a deux dimensions */
int *a[]; /* pointeur de tableau d'entier a une dimension */
int **a; /* pointeur de pointeur d'entier */
> Finalement a[3] est équivalent à *(a+3) bien que selon logique les deux
> ne devraient pas être exactement la même chose quoique du côté du
> langage machine l'implémentation est exactement la même (et c'est
> d'ailleurs pour cela que ça ne me pose pas de problème de
> compréhension).
Il importe aussi de ne pas ramener tous les pointeurs a des entiers pour une
autre raison. Ce qui permet d'ecrire *(a + 3) ne peut se faire que si 'a' a
ete defini comme un 'int *'. En effet, le compilateur reconnaitra que, comme
il s'agit d'un pointeur d'entier, celui-ci doit etre incremente pas pas de 4.
Si a est defini comme un simple 'int', non seulement nous aurons un warning
(peut-etre meme une erreur de compilation), mais l'incrementation ne se fera
pas correctement. On peut "bricoler" du casting pour eviter les warnings,
mais si on ne le fait pas correctement, on risque quand meme le bus-error. Si
par malchance, on ecrit *(a + 4), on n'aura pas de bus error mais on accedera
seulement au quart des valeurs du tableau :-). Remarque que cette o[eration
ne compilera pas non-plus si a est de type '* void'...
Tout ca pour dire qu'il est plus simple, logique et cense d'ecrire et de
definir le type de pointeur que l'on manipule, plutot que de dire que tous
les pointeurs sont des 'int' (ce qui est faux, je le repete).
> Et j'ai d'ailleurs eu un problème similaire la fois ou j'ai du
> transmettre des structures binaires entre deux machines avec des
> architectures différentes. Il m'a fallu jouer avec la position des
> variables pour que ça marche. C'est pourri mais c'était ce qui était
> demandé pour des raison de vitesse de traitement.
>
> C'est un code ou il faudra faire attention à chaque opération de
> maintenance.
On peut soit utiliser ce qui est fournit par xdr (issu de NFS)m, soit, comme
je l'ai fait, developper une librairie d'echange d'objets avec reconnaissance
des objets de chaque cote (chargement dynamique de la librairie), incluant
des fonction wrap/unwrap dans cahcune des versiond e lalibrairie. L'avantage
suplementaire est que j'ai aussi pu inclure une notion de 'version' des
objects ce qui me permettait de pouvoir m'affranchir de la necessite de
synchronisation des applications. Parfois, il ne faut pas attendre la
solution miracle mais la developper soi-meme.
> Oui, mais en utilisant la variable au lieu de la macro on obtient le
> même résultat et je trouve que c'est quand même plus lisible si on ne
> met pas de macro. Soit dit en passant, j'adore les macros du C, ça
> permet de faire beaucoup de choses (entre autre augmenter la lisibilité
> du code même si ça peut paraître étrange à quelqu'un qui n'est pas
> habitué), mais si je peux obtenir le même résultat (en fonctionnel et en
> lisibilité) sans les utiliser c'est mieux.
Une variable ou une macro... ce n'est pas non plus pareil. L'objectif de la
macro est de permettre au compilateur d'effectuer son travail
d'optimisation ! ALors que cela n'est plus possible si l'information est
encapsulee dans une variable. Il se trouve que les compilateurs sont capables
d'effectuer des optimisations impresionnantes lorsque l'on manipule des
pointeurs. Le compilateur le sait et agit en consequence. C'est
particulierement vrais sur les architectures de type EPIC et WLIW.
> Les deux, et ne t'en fais pas, ce n'est pas qu'un problème de LL,
> beaucoup de ces problèmes je les ai vu aussi en milieu industriel ou
> l'impératif est sortir un code fonctionnel le plus vite possible et au
> moindre coût.
Certains constructuers *NIX ont collectes les mauvaises notes a la pelle dans
ce domaine. Pour d'autres, un gros travail avait ete fait pour rendre tous
les includes standard ANSI/POSIX/etc. et c'etait un vrais bonheur.
> Donc les programmeurs sont rarement poussés à faire mieux.
Jamais... mieux c'est plus vite :-)
> Tu écris un code pourri mais qui marche sur la machine XY et tu as mis
> la moitié du temps? Très bien.
> Est-ce que ce code marchera sur une autre architecture sans tout
> retravailler? on s'en fout, le client ne l'a pas demandé et le jour ou
> il le demandera il va payer la migration et alors on aura un autre
> projet et un autre budget pour améliorer le code.
SI pas de contraintes trop importantes au niveau vitesse d'execution...
Python, Java... est une reponse. L'avantahe de Python etant que
l'interpreteur se comporte de la meme maniere sur toutes les machines, alors
que les differentes JVM tendent a agacer...
dc
More information about the gull
mailing list