[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