OVH Cloud OVH Cloud

Question sur realloc

67 réponses
Avatar
Kevin Denis
Bonjour,

j'ai une question sur realloc:
$ cat testrealloc.c
#include <stdio.h>
#include <stdlib.h>

int main(void){
char * c=NULL;
c=malloc(8);
if (c == NULL) return -1;
c="abcdefgh";
printf("valeur du pointeur: %d\r\n",c);
printf("valeur de c: %s\r\n",c);
c=realloc(c,16);
return 0;
}
$ gcc -Wall -o testrealloc testrealloc.c
testrealloc.c: In function 'main':
testrealloc.c:9: attention : format '%d' expects type 'int', but argument 2
has type 'char *'
$ ./testrealloc
valeur du pointeur: 134514000
valeur de c: abcdefgh
Erreur de segmentation

Ma question est: pourquoi le realloc segfaulte?
Merci
--
Kevin

10 réponses

1 2 3 4 5
Avatar
Pascal J. Bourguignon
Kevin Denis writes:

Le 03-05-2011, Marc Espie a écrit :
Il y a une autre ENORME difference entre les deux.

Dans le premier cas:
char *str = "abcdefgh";
tu prends une chaine de caractere et tu stockes son adresse dans str.



Ok, et donc je peux modifier l'adresse de str, mais pas modifier les
données pointées par str.



No. Si comme c'est écrit ici, str est une variable automatique, tu ne
peux pas modifier son adresse.

Quoi que tu fasse, printf("%x",&str); imprimera toujours la même chose
(dans la même invocation de la fonction où la variable automatique str
est créée).


Comme le pointeur stoqué dans str pointe sur une donnée literale, tu
ne peux effectivement pas modifier cette donnée literale.


Mais tu peux modifier le pointeur stoqué dans str!


#include <stdio.h>
#include <string.h>

int main(){
char* str="Bonjour Le Monde!";
printf("Before newstr %p -> %p [] %sn",&str,str,str);
str=strdup(str);
printf("After newstr %p -> %p [] %sn",&str,str,str);
strncpy(str,"Bye bye",7);
printf("After strncpy %p -> %p [] %sn",&str,str,str);
return(0);
}


-*- mode: compilation; default-directory: "/tmp/" -*-
Compilation started at Thu May 5 12:48:52

SRC="/tmp/e.c" ; EXE="e" ; gcc -I. -L. -g3 -ggdb3 -o ${EXE} ${SRC} && ./${EXE} && echo status = $?
Before newstr 0x7fff44858108 -> 0x400770 [] Bonjour Le Monde!
After newstr 0x7fff44858108 -> 0x602010 [] Bonjour Le Monde!
After strncpy 0x7fff44858108 -> 0x602010 [] Bye bye Le Monde!
status = 0

Compilation finished at Thu May 5 12:48:52



Donc si je veux modifier la chaine de caractères,
je dois écrire des caractères ailleurs, puis faire pointer str vers
le premier caractère de la chaine?



Oui.



Pourquoi ? parce que la chaine est stockee avec les donnees en RO



Ca se paramètre, non? Le programme en RO, c'est lié à openBSD?



C'est possible que ce soit possible, mais c'est assez difficile de
trouver les bonnes options. En particulier, si on laisse faire le
compilateur, il peut fusionner les chaînes literales:


int main(){
const char* a="Bonjour Le Monde";
const char* b="Le Monde";
const char* c="de";
printf("%d %dn",c-b,b-a);
return(0);
}


/*
-*- mode: compilation; default-directory: "/tmp/" -*-
Compilation started at Thu May 5 12:52:22

SRC="/tmp/d.c" ; EXE="d" ; gcc -I. -L. -g3 -ggdb3 -o ${EXE} ${SRC} && ./${EXE} && echo status = $?
9 17
status = 0

Compilation finished at Thu May 5 12:52:22


-*- mode: compilation; default-directory: "/tmp/" -*-
Compilation started at Thu May 5 12:52:47

SRC="/tmp/d.c" ; EXE="d" ; gcc -I. -L. -O3 -o ${EXE} ${SRC} && ./${EXE} && echo status = $?
6 8
status = 0

Compilation finished at Thu May 5 12:52:47

*/

Avec les premières options de compilation, le compilateur alloue des
blocs indépendants pour chaque chaine.

Avec les deuxième options de compilation, comme les chaînes sont
literale, il peut les placer en mémoire à lecture seule,
et par conséquent il peut se permettre de superposer les chaînes qui
sont une terminaison d'une autre.


et donc
que la modif, str[2], est inoperante.





Avec un bon système d'exploitation, ça devrait susciter une erreur de
segmentation (signal SIGSEG sur unix), car on essaye un accès à un
segment de mémoire qui n'est pas autorisé.


(c'est non conforme car la ligne str[2] = 'e'; n'a pas de comportement defini)

Si je remplace par
char str[] = "abcdefgh";
ca va afficher
abedefgh

c'est tres different: je cree un tableau de caracteres que j'initialise
avec les valeurs de la chaine "abcdefgh". Cas tres particulier: je n'ai
pas besoin de preciser la taille du tableau, le compilo se debrouille, et
va me faire un tableau de pile-poil 9 octets.



Mais si dans ce 2e exemple, je fais:
printf("%sn", str);
Le printf s'arrête car il rencontre un . Mais alors si me prends
l'idée de faire:
str[8] = 'e';
alors le printf va lire abcdefghe et ensuite? Tous les octets jusqu'a
rencontrer un ou sortir de la zone allouée (et donc une violation
de mémoire, i.e. segfault)?



Au choix, le premier qui arrive.

Le plus souvent, la mémoire est remplie de zéro, (par exemple, on
travaille beaucoup plus avec des entiers entre 0 et 100, donc trois
octets à zéro pour un octet non nul, qu'avec des entiers entre 16777216
et 4294967295 (et encore, il y en a beaucoup entre 16777216 et
4294967295 qui ont au moins un octet à zéro). Ceci agrâve le problème,
puisque ça signifie qu'avec une telle erreur, le programme continue son
exécution avec des données illégale, et peut être en modifiant des
données qu'il ne devrait pas (donc possibilité d'exploit en dépassement
de tampon).

Si on a de la chance, mais c'est rare, ce qui se trouve après le bloc
mémoire en question, c'est une page non allouée ou une page allouée avec
restriction des droits d'accès, ce qui provoquera immédiatement une
erreur (signal SIGBUS ou SIGSEG). Mais les probabilités sont contre
nous.




La leçon de tout ça, c'est qu'à moins d'être dans la situation de
programmer une machine à laver avec un microprocesseur et moins d'un
kilo octets de RAM, IL NE FAUT PAS UTILISER C. À tout le moins, il ne
faut pas utiliser les fonctions de bibliothèque standard C qui sont de
trop bas niveau.

Pour les chaînes, les tableaux, etc, il faut utiliser des bibliothèques
qui proposent des abstractions et dont l'implémentation effectue les
vérifications à l'exécution qui ne sont pas faites par les compilateurs.
(voir par exemple http://bstring.sourceforge.net/)

Mais c'est plus un problème de compilateur que de langage, la norme C
n'interdit pas les vérification, simplement elle permet aux
implémentations de faire n'importe quoi. Si les clients disaient: ah
non, je ne peux pas utiliser ce compilateur C, car il n'effectue pas de
vérification à l'exécution, les choses se passeraient mieux.


--
__Pascal Bourguignon__ http://www.informatimago.com/
A bad day in () is better than a good day in {}.
Avatar
Alexandre Bacquart
On 05/05/2011 11:00 AM, Kevin Denis wrote:
Le 03-05-2011, Marc Espie a écrit :
Il y a une autre ENORME difference entre les deux.

Dans le premier cas:
char *str = "abcdefgh";
tu prends une chaine de caractere et tu stockes son adresse dans str.



Ok, et donc je peux modifier l'adresse de str, mais pas modifier les
données pointées par str.



Oui, en théorie (ce qui revient à dire que c'est mal de tenter de le faire).

Donc si je veux modifier la chaine de caractères,
je dois écrire des caractères ailleurs, puis faire pointer str vers
le premier caractère de la chaine?



Hum... soit tu formules mal, soit ton raisonnement est faux (je vais
opter pour le second cas). Dans :

char *str = "abcdefgh";

Il y a deux choses : la chaîne "abcdefgh" (que tu ne peux pas modifier)
et le pointeur str (initialisé sur cette chaîne). Alors que dans :

char tab[] = "abcdefgh";

Il n'y a que la chaîne (toujours pas modifiable) *identifiée* par s.
Aucun pointeur. Ici, tab n'est pas un pointeur, mais identifie la chaîne.

Quand tu utilises tab ailleurs dans le programme, il se passe
quelque-chose d'assez subtil : un pointeur temporaire est créé pour que
tab se comporte comme un pointeur, comme dans :

printf("%sn", s);

Le pointeur temporaire est détruit juste après son utilisation.


Or, les chaines de caracteres sont des constantes. Par exemple, soit
le mini programme (non conforme) suivant:
#include<stdio.h>
int main()
{
char *str = "abcdefgh";
str[2] = 'e';
printf("%sn", str);
return 0;
}

chez moi, si je le compile et l'execute, il affiche...
abcdefgh

Pourquoi ? parce que la chaine est stockee avec les donnees en RO



Ca se paramètre, non? Le programme en RO, c'est lié à openBSD?



RO == Read Only (lecture seule).

Il voulait dire que la chaîne est stockée dans une zone mémoire supposée
être en lecture seule.

Ne pas confondre avec la ROM, ça n'a bien sûr rien à voir, même si le
principe est le même. Quand on parle de mémoire RO en programmation,
généralement, il s'agit d'une zone RAM supposée n'être accessible qu'en
lecture. C'est le système d'exploitation qui crée ces zones quand il
charge un programme.

et donc
que la modif, str[2], est inoperante.

(c'est non conforme car la ligne str[2] = 'e'; n'a pas de comportement defini)

Si je remplace par
char str[] = "abcdefgh";
ca va afficher
abedefgh

c'est tres different: je cree un tableau de caracteres que j'initialise
avec les valeurs de la chaine "abcdefgh". Cas tres particulier: je n'ai
pas besoin de preciser la taille du tableau, le compilo se debrouille, et
va me faire un tableau de pile-poil 9 octets.



Mais si dans ce 2e exemple, je fais:
printf("%sn", str);
Le printf s'arrête car il rencontre un . Mais alors si me prends
l'idée de faire:
str[8] = 'e';
alors le printf va lire abcdefghe et ensuite?



Ensuite c'est au petit bonheur la chance ! On ne doit pas modifier une
chaîne statique. En l'occurrence ici, tu écrases le '' final (mais
comme c'est un comportement indéfini d'écrire dans une chaîne statique,
la tentative peut tout aussi bien échouer sur un segfault). printf() va
tenter d'afficher la chaîne jusqu'à trouver un '' quelque-part (qui
peut être très éloigné, auquel cas tu auras beaucoup de bruit après
l'affichage de la chaîne, c'est évidement un bug).


--
Alex
Avatar
Alexandre Bacquart
On 05/05/2011 01:06 PM, Alexandre Bacquart wrote:

char tab[] = "abcdefgh";

Il n'y a que la chaîne (toujours pas modifiable) *identifiée* par s.
Aucun pointeur. Ici, tab n'est pas un pointeur, mais identifie la chaîne.

Quand tu utilises tab ailleurs dans le programme, il se passe
quelque-chose d'assez subtil : un pointeur temporaire est créé pour que
tab se comporte comme un pointeur, comme dans :

printf("%sn", s);

Le pointeur temporaire est détruit juste après son utilisation.



Je voulais dire :

printf("%sn", tab);


--
Alex
Avatar
Erwan David
Alexandre Bacquart écrivait :


RO == Read Only (lecture seule).

Il voulait dire que la chaîne est stockée dans une zone mémoire
supposée être en lecture seule.

Ne pas confondre avec la ROM, ça n'a bien sûr rien à voir, même si le
principe est le même. Quand on parle de mémoire RO en programmation,
généralement, il s'agit d'une zone RAM supposée n'être accessible
qu'en lecture. C'est le système d'exploitation qui crée ces zones
quand il charge un programme.



Note qu'en embarqué il y a toutes les chances pour que ce soit
effectivement du read-only, ou au moins dans une zone hors de la RAM
(flash ou eeprom). Ce n'est pas lié à un OS, qui d'ailleurs n'existe
peut-être pas...


[...]


Ensuite c'est au petit bonheur la chance ! On ne doit pas modifier une
chaîne statique. En l'occurrence ici, tu écrases le '' final (mais
comme c'est un comportement indéfini d'écrire dans une chaîne
statique, la tentative peut tout aussi bien échouer sur un
segfault). printf() va tenter d'afficher la chaîne jusqu'à trouver un
' quelque-part (qui peut être très éloigné, auquel cas tu auras
beaucoup de bruit après l'affichage de la chaîne, c'est évidement un
bug).



Voire fera que printf essaiera de lire certains regsitres dont la
lecture va provoquer un comportement spécifique de la plateforme,
pouvant aller jusqu'à l'effacement complet de la mémoire (par exemple
dans un HSM qui prendrait ça pour une tentative d'attaque...)

QUand on dit qu'un comportement indéfinui peut provoquer des
catastrophes, c'est pas exagérer...

--
Le travail n'est pas une bonne chose. Si ça l'était,
les riches l'auraient accaparé
Avatar
espie
In article ,
Pascal J. Bourguignon wrote:
Kevin Denis writes:

Le 03-05-2011, Marc Espie a écrit :
Il y a une autre ENORME difference entre les deux.

Dans le premier cas:
char *str = "abcdefgh";
tu prends une chaine de caractere et tu stockes son adresse dans str.



Ok, et donc je peux modifier l'adresse de str, mais pas modifier les
données pointées par str.



No. Si comme c'est écrit ici, str est une variable automatique, tu ne
peux pas modifier son adresse.



Huh ?

char *str = "abcdefgh";
char *s = "bonjour";
str = s;
marche tres bien ....
Avatar
espie
In article <4dc2849b$0$27967$,
Alexandre Bacquart wrote:
On 05/05/2011 11:00 AM, Kevin Denis wrote:
Le 03-05-2011, Marc Espie a écrit :
Il y a une autre ENORME difference entre les deux.

Dans le premier cas:
char *str = "abcdefgh";
tu prends une chaine de caractere et tu stockes son adresse dans str.



Ok, et donc je peux modifier l'adresse de str, mais pas modifier les
données pointées par str.



Oui, en théorie (ce qui revient à dire que c'est mal de tenter de le faire).

Donc si je veux modifier la chaine de caractères,
je dois écrire des caractères ailleurs, puis faire pointer str vers
le premier caractère de la chaine?



Hum... soit tu formules mal, soit ton raisonnement est faux (je vais
opter pour le second cas). Dans :

char *str = "abcdefgh";

Il y a deux choses : la chaîne "abcdefgh" (que tu ne peux pas modifier)
et le pointeur str (initialisé sur cette chaîne). Alors que dans :

char tab[] = "abcdefgh";

Il n'y a que la chaîne (toujours pas modifiable) *identifiée* par s.
Aucun pointeur. Ici, tab n'est pas un pointeur, mais identifie la chaîne.



Ta vision des choses est legerement differente de la realite.


Dans ce code, au moment de l'execution, il n'y a pas de chaine "abcdefgh".

La chaine n'a qu'une existence pendant la compilation, c'est un initialiseur
pour un tableau. Le code ecrit est strictement equivalent a:
char tab[9] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 0 };
ce que tu as la, c'est juste du sucre syntaxique:
- on peut ecrire un initialiseur d'un tableau de caracteres comme une chaine,
ca initialisera chacun des elements du tableau au code du caractere
correspondant.
- tu peux omettre la taille du tableau, elle est deduite de l'initialiseur.

Quand tu utilises tab ailleurs dans le programme, il se passe
quelque-chose d'assez subtil : un pointeur temporaire est créé pour que
tab se comporte comme un pointeur, comme dans :



Non. Rien n'est cree. Un tableau, dans un contexte qui a besoin d'un pointeur,
est considere exactement comme un pointeur, constant de sucroit.

printf("%sn", s);

Le pointeur temporaire est détruit juste après son utilisation.



Non plus.
Avatar
Pascal J. Bourguignon
(Marc Espie) writes:

In article ,
Pascal J. Bourguignon wrote:
Kevin Denis writes:

Le 03-05-2011, Marc Espie a écrit :
Il y a une autre ENORME difference entre les deux.

Dans le premier cas:
char *str = "abcdefgh";
tu prends une chaine de caractere et tu stockes son adresse dans str.



Ok, et donc je peux modifier l'adresse de str, mais pas modifier les
données pointées par str.



No. Si comme c'est écrit ici, str est une variable automatique, tu ne
peux pas modifier son adresse.



Huh ?

char *str = "abcdefgh";
char *s = "bonjour";
str = s;
marche tres bien ....



Oui, mais ça n'a pas changé l'adresse de str. Lis mes programmes!

--
__Pascal Bourguignon__ http://www.informatimago.com/
A bad day in () is better than a good day in {}.
Avatar
Alexandre Bacquart
On 05/05/2011 04:19 PM, Marc Espie wrote:
In article<4dc2849b$0$27967$,
Alexandre Bacquart wrote:
On 05/05/2011 11:00 AM, Kevin Denis wrote:
Le 03-05-2011, Marc Espie a écrit :
Il y a une autre ENORME difference entre les deux.

Dans le premier cas:
char *str = "abcdefgh";
tu prends une chaine de caractere et tu stockes son adresse dans str.



Ok, et donc je peux modifier l'adresse de str, mais pas modifier les
données pointées par str.



Oui, en théorie (ce qui revient à dire que c'est mal de tenter de le faire).

Donc si je veux modifier la chaine de caractères,
je dois écrire des caractères ailleurs, puis faire pointer str vers
le premier caractère de la chaine?



Hum... soit tu formules mal, soit ton raisonnement est faux (je vais
opter pour le second cas). Dans :

char *str = "abcdefgh";

Il y a deux choses : la chaîne "abcdefgh" (que tu ne peux pas modifier)
et le pointeur str (initialisé sur cette chaîne). Alors que dans :

char tab[] = "abcdefgh";

Il n'y a que la chaîne (toujours pas modifiable) *identifiée* par s.
Aucun pointeur. Ici, tab n'est pas un pointeur, mais identifie la chaîne.



Ta vision des choses est legerement differente de la realite.



Je ne crois pas.

Dans ce code, au moment de l'execution, il n'y a pas de chaine "abcdefgh".



Il faut bien qu'elle soit quelque-part. Insinuerais-tu que c'est une
chaîne procédurale ? :) Je ne sais pas d'ailleurs si la norme autorise
implicitement ce genre de pratique, mais en général, la chaîne est bel
et bien dans le segment de données à l'exécution. Dire qu'il n'y a pas
de chaîne me semble confus, au contraire.

La chaine n'a qu'une existence pendant la compilation, c'est un initialiseur
pour un tableau. Le code ecrit est strictement equivalent a:
char tab[9] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 0 };
ce que tu as la, c'est juste du sucre syntaxique:



Oui. Quitte à chipoter (et être plus clair), j'aurais plutôt écris ''
à la fin, mais oui... le sucre syntaxique, c'est super, mais ça
contribue à cacher des mécanismes qui autrement apparaîtraient plus
nettement aux débutants.

- on peut ecrire un initialiseur d'un tableau de caracteres comme une chaine,
ca initialisera chacun des elements du tableau au code du caractere
correspondant.
- tu peux omettre la taille du tableau, elle est deduite de l'initialiseur.

Quand tu utilises tab ailleurs dans le programme, il se passe
quelque-chose d'assez subtil : un pointeur temporaire est créé pour que
tab se comporte comme un pointeur, comme dans :



Non. Rien n'est cree. Un tableau, dans un contexte qui a besoin d'un pointeur,
est considere exactement comme un pointeur, constant de sucroit.



Dans le code généré, la chaîne est accédé à travers un registre
d'adresse, l'équivalent en assembleur d'un pointeur. Quand je dis
"temporaire", je ne suppose pas qu'un emplacement mémoire temporaire est
créé à son intention (encore que le compilateur fait bien ce qu'il
veut). Mais cela me semble plus simple de l'expliquer comme cela.


--
Alex
Avatar
Kevin Denis
Le 05-05-2011, Pascal J. Bourguignon a écrit :

Oui, mais ça n'a pas changé l'adresse de str. Lis mes programmes!



L'adresse de str n'a pas changée, mais la valeur stockée, elle, à changée
et pointe vers autre chose si j'ai bien compris?
--
Kevin
Avatar
Alexandre Bacquart
On 05/05/2011 04:51 PM, Kevin Denis wrote:
Le 05-05-2011, Pascal J. Bourguignon a écrit :

Oui, mais ça n'a pas changé l'adresse de str. Lis mes programmes!



L'adresse de str n'a pas changée, mais la valeur stockée, elle, à changée
et pointe vers autre chose si j'ai bien compris?



Oui, le pointeur a son emplacement propre qui ne change pas. La valeur
stockée dans le pointeur, c'est une adresse en mémoire qui n'a rien à
voir avec l'adresse du pointeur lui-même.


--
Alex
1 2 3 4 5