Où ça coince:
---------
$ gdb a.out
(...)
(gdb) run
Starting program: /home/follc/tmp/a.out
Program received signal SIGSEGV, Segmentation fault.
main () at test.s:17
17 movb $65, (%eax)
Current language: auto; currently asm
----------
En y regardant de plus prêt:
----------
(gdb) b 17
Breakpoint 1 at 0x8048356: file test.s, line 17.
(gdb) run
Breakpoint 1, main () at test.s:17
17 movb $65, (%eax)
(gdb) info registers
eax 0x8048434 134513716
ecx 0xbfec4f2c -1075032276
edx 0xbfec4f24 -1075032284
ebx 0x81efdc 8515548
esp 0xbfec4e90 0xbfec4e90
ebp 0xbfec4e98 0xbfec4e98
esi 0x1 1
edi 0x8210dc 8523996
eip 0x8048356 0x8048356
eflags 0x286 646
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) x/2x 0x8048434
0x8048434 <_IO_stdin_used+4>: 0x6a6e6f42 0x0072756f
------------
0x8048434 on a bien le contenu de la chaine.
Qu'est ce qui se passe ?
En fait je voudrais bien admettre qu'aucun des deux ne fonctionnent.
Mais je ne comprend pas que l'un fonctionne quand l'autre ne fonctionne pas.
Le code assembleur me semble correct. Qu'est ce qui se passe ?
Le 17/01/05 23:18, dans <41ec39a4$0$25786$, « Cedric Foll » a écrit :
char *ptr="Bonjour"; *ptr='A'; // crash
Gcc sous Linux x86 place les chaînes de caractère dans une zone data en readonly. Tout essai pour y écrire entraîne donc une exception.
char ptr[]="Bonjour";
Là, ptr est un tableau en zone data read-write initialisée par des caractères. On peut y lire et y écrire des données.
-- Éric Lévénez -- <http://www.levenez.com/> Unix is not only an OS, it's a way of life.
Nicolas MERCIER
On Mon, 17 Jan 2005 23:18:09 +0100 Cedric Foll wrote:
Bonjour,
Je travaille avec gcc-3.3.3 sous fedora.
J'ai un crash dans le code suivant à "*ptr='A';": [..]
Qu'est ce qui se passe ?
En fait je voudrais bien admettre qu'aucun des deux ne fonctionnent. Mais je ne comprend pas que l'un fonctionne quand l'autre ne fonctionne pas.
Le code assembleur me semble correct. Qu'est ce qui se passe ?
Donc où est le problème ? En résumé :
char *ptr = "Bonjour" crée une variable pointeur qui pointe sur le dé but de la chaîne de caractère "Bonjour" qui elle est stockée ailleurs (en général en read only) char ptr[] = "Bonjour" crée un tableau de caractère entièrement sto cké sur la pile, de 8 éléments, dont le premier est initialisé à 'B', le deuxième à... enfin, tu vois le genre. L'instruction suivante, *ptr = 'A', essaye : - dans le premier cas d'aller modifier les données pointées par ptr, l à ou elles sont (en read only) - dans le deuxième cas, de changer la valeur du premier élément du tableau qui lui est sur la pile.
En gros, la chaîne est toujours stockée en read only mais dans le deuxième cas elle est recopiée dans le tableau.
On Mon, 17 Jan 2005 23:18:09 +0100
Cedric Foll <cedric.foll@ac-rouen.fr> wrote:
Bonjour,
Je travaille avec gcc-3.3.3 sous fedora.
J'ai un crash dans le code suivant à "*ptr='A';":
[..]
Qu'est ce qui se passe ?
En fait je voudrais bien admettre qu'aucun des deux ne fonctionnent.
Mais je ne comprend pas que l'un fonctionne quand l'autre ne
fonctionne pas.
Le code assembleur me semble correct. Qu'est ce qui se passe ?
Donc où est le problème ?
En résumé :
char *ptr = "Bonjour" crée une variable pointeur qui pointe sur le dé but
de la chaîne de caractère "Bonjour" qui elle est stockée ailleurs (en
général en read only)
char ptr[] = "Bonjour" crée un tableau de caractère entièrement sto cké
sur la pile, de 8 éléments, dont le premier est initialisé à 'B', le
deuxième à... enfin, tu vois le genre.
L'instruction suivante, *ptr = 'A', essaye :
- dans le premier cas d'aller modifier les données pointées par ptr, l à
ou elles sont (en read only)
- dans le deuxième cas, de changer la valeur du premier élément du
tableau qui lui est sur la pile.
En gros, la chaîne est toujours stockée en read only mais dans le
deuxième cas elle est recopiée dans le tableau.
On Mon, 17 Jan 2005 23:18:09 +0100 Cedric Foll wrote:
Bonjour,
Je travaille avec gcc-3.3.3 sous fedora.
J'ai un crash dans le code suivant à "*ptr='A';": [..]
Qu'est ce qui se passe ?
En fait je voudrais bien admettre qu'aucun des deux ne fonctionnent. Mais je ne comprend pas que l'un fonctionne quand l'autre ne fonctionne pas.
Le code assembleur me semble correct. Qu'est ce qui se passe ?
Donc où est le problème ? En résumé :
char *ptr = "Bonjour" crée une variable pointeur qui pointe sur le dé but de la chaîne de caractère "Bonjour" qui elle est stockée ailleurs (en général en read only) char ptr[] = "Bonjour" crée un tableau de caractère entièrement sto cké sur la pile, de 8 éléments, dont le premier est initialisé à 'B', le deuxième à... enfin, tu vois le genre. L'instruction suivante, *ptr = 'A', essaye : - dans le premier cas d'aller modifier les données pointées par ptr, l à ou elles sont (en read only) - dans le deuxième cas, de changer la valeur du premier élément du tableau qui lui est sur la pile.
En gros, la chaîne est toujours stockée en read only mais dans le deuxième cas elle est recopiée dans le tableau.
Horst Kraemer
Cedric Foll wrote:
Bonjour,
Je travaille avec gcc-3.3.3 sous fedora.
J'ai un crash dans le code suivant à "*ptr='A';": --------------- [ tmp]$ cat > test.c #include <stdio.h> int main() { char *ptr="Bonjour"; *ptr='A'; // crash return 0; } [ tmp]$ gcc -g test.c [ tmp]$ gdb a.out (...) (gdb) run Starting program: /home/follc/tmp/a.out
Program received signal SIGSEGV, Segmentation fault. 0x08048356 in main () at test.c:5 5 *ptr='A'; // crash ---------------------
Admétons que celà ne marche pas parce que j'essaie de modifier la valeur d'une chaine qui est constante.
Mais le code suivant fonctionne: ---------------- [ tmp]$ cat > test2.c #include <stdio.h> int main() { char ptr[]="Bonjour"; *ptr='A'; // ok return 0; } -----------------
Quel est la différence entre les deux déclarations ?
char *ptr="Bonjour";
définit un pointeur initialisé de facon qu'il pointe vers une chaine qui se trouve dans une zone de mémoire réservée pour des chaines littérales. La norme permet au compilateur de limiter cette zone de mémoire à la lecture. Donc un essai de changer les caractères peut faire planter le programme. La technique conseillée dans ce cas est de définir un pointeur vers char constant
const char *ptr="Bonjour"
Cependant
char ptr[]="Bonjour";
définit un tableau de char dans la mémoire qui appartient au programmeur initialisé par la chaine "Bonjour".
-- Horst
Cedric Foll <cedric.foll@ac-rouen.fr> wrote:
Bonjour,
Je travaille avec gcc-3.3.3 sous fedora.
J'ai un crash dans le code suivant à "*ptr='A';":
---------------
[follc@follc tmp]$ cat > test.c
#include <stdio.h>
int main()
{
char *ptr="Bonjour";
*ptr='A'; // crash
return 0;
}
[follc@follc tmp]$ gcc -g test.c
[follc@follc tmp]$ gdb a.out
(...)
(gdb) run
Starting program: /home/follc/tmp/a.out
Program received signal SIGSEGV, Segmentation fault.
0x08048356 in main () at test.c:5
5 *ptr='A'; // crash
---------------------
Admétons que celà ne marche pas parce que j'essaie de modifier la valeur
d'une chaine qui est constante.
Mais le code suivant fonctionne:
----------------
[follc@follc tmp]$ cat > test2.c
#include <stdio.h>
int main()
{
char ptr[]="Bonjour";
*ptr='A'; // ok
return 0;
}
-----------------
Quel est la différence entre les deux déclarations ?
char *ptr="Bonjour";
définit un pointeur initialisé de facon qu'il pointe vers une chaine
qui se trouve dans une zone de mémoire réservée pour des chaines
littérales. La norme permet au compilateur de limiter cette zone de
mémoire à la lecture. Donc un essai de changer les caractères peut
faire planter le programme. La technique conseillée dans ce cas est de
définir un pointeur vers char constant
const char *ptr="Bonjour"
Cependant
char ptr[]="Bonjour";
définit un tableau de char dans la mémoire qui appartient au
programmeur initialisé par la chaine "Bonjour".
J'ai un crash dans le code suivant à "*ptr='A';": --------------- [ tmp]$ cat > test.c #include <stdio.h> int main() { char *ptr="Bonjour"; *ptr='A'; // crash return 0; } [ tmp]$ gcc -g test.c [ tmp]$ gdb a.out (...) (gdb) run Starting program: /home/follc/tmp/a.out
Program received signal SIGSEGV, Segmentation fault. 0x08048356 in main () at test.c:5 5 *ptr='A'; // crash ---------------------
Admétons que celà ne marche pas parce que j'essaie de modifier la valeur d'une chaine qui est constante.
Mais le code suivant fonctionne: ---------------- [ tmp]$ cat > test2.c #include <stdio.h> int main() { char ptr[]="Bonjour"; *ptr='A'; // ok return 0; } -----------------
Quel est la différence entre les deux déclarations ?
char *ptr="Bonjour";
définit un pointeur initialisé de facon qu'il pointe vers une chaine qui se trouve dans une zone de mémoire réservée pour des chaines littérales. La norme permet au compilateur de limiter cette zone de mémoire à la lecture. Donc un essai de changer les caractères peut faire planter le programme. La technique conseillée dans ce cas est de définir un pointeur vers char constant
const char *ptr="Bonjour"
Cependant
char ptr[]="Bonjour";
définit un tableau de char dans la mémoire qui appartient au programmeur initialisé par la chaine "Bonjour".
-- Horst
Pierre Maurette
Bonjour,
Je travaille avec gcc-3.3.3 sous fedora. [...]
Le code assembleur me semble correct. Qu'est ce qui se passe ?
Donc où est le problème ? La réponse C a été donnée. Personnellement, je trouve zarbi que le
compilo puisse légalement initialiser une variable automatique pointeur non constant par la valeur d'un pointeur constant, mais c'est comme ça.
Pour l'interprétation du code assembleur, j'ai refait un bout de code sous gcc 3.2 / mingw32, donc sous Windows (mais on retrouve vos bouts Linux):
[segment de donnés initialisées] LC0: .ascii "Bonjour " (gcc optimise les chaînes dupliquées)
[prologue zappé] Dans le prologue, entr'autre, de la place est réservée sur la pile, et EBP positionné en conséquence. EBP ne sera plus modifié dans la fonction, et l'adressage par rapport à EBP accèdera aux données locales (et aux paramètres).
[char* ptr1 = "Bonjour";] movl $LC0, -4(%ebp) # ptr1 (gcc a réservé un pointeur dans la pile, en EBP - 4, et l'initialise par l'adresse de la chaîne dans le segment de données initialisées)
[char ptr2[] = "Bonjour";] movl LC0, %eax movl LC0+4, %edx movl %eax, -16(%ebp) # ptr2 movl %edx, -12(%ebp) # ptr2 (gcc a réservé le tableau dans la pile, de EBP - 16 à EBP - 9, et remplit cette zone par le contenu de LC0. L'utilisation de EAX/EDX est une "ruse" tenant compte du fait que "Bonjour " fait 8 char. Le cas général est une recopie par boucle)
[*ptr1 = 'A';] movl -4(%ebp), %eax # ptr1 movb $65, (%eax) (gcc copie dans EAX l'adresse de LC0 qui est en EBP - 4, puis modifie la mémoire pointée par EAX, donc en zone de données initialisées -> crash)
[*ptr2 = 'C';] movb $67, -16(%ebp) (gcc modifie la mémoire en EBP - 16, c'est à dire le premier élément de ptr2[], pas de problème)
[etc. etc.]
Remarquez que le compilateur Borland réserve les chaînes dans une zone RW. Mais si on prend soin de commuter l'option "Merge duplicated strings", le code suivant:
[segment de donnés initialisées]
LC0:
.ascii "Bonjour "
(gcc optimise les chaînes dupliquées)
[prologue zappé]
Dans le prologue, entr'autre, de la place est réservée sur la pile, et
EBP positionné en conséquence. EBP ne sera plus modifié dans la
fonction, et l'adressage par rapport à EBP accèdera aux données locales
(et aux paramètres).
[char* ptr1 = "Bonjour";]
movl $LC0, -4(%ebp) # ptr1
(gcc a réservé un pointeur dans la pile, en EBP - 4, et l'initialise par
l'adresse de la chaîne dans le segment de données initialisées)
[char ptr2[] = "Bonjour";]
movl LC0, %eax
movl LC0+4, %edx
movl %eax, -16(%ebp) # ptr2
movl %edx, -12(%ebp) # ptr2
(gcc a réservé le tableau dans la pile, de EBP - 16 à EBP - 9, et
remplit cette zone par le contenu de LC0. L'utilisation de EAX/EDX est
une "ruse" tenant compte du fait que "Bonjour " fait 8 char. Le cas
général est une recopie par boucle)
[*ptr1 = 'A';]
movl -4(%ebp), %eax # ptr1
movb $65, (%eax)
(gcc copie dans EAX l'adresse de LC0 qui est en EBP - 4, puis modifie la
mémoire pointée par EAX, donc en zone de données initialisées -> crash)
[*ptr2 = 'C';]
movb $67, -16(%ebp)
(gcc modifie la mémoire en EBP - 16, c'est à dire le premier élément de
ptr2[], pas de problème)
[etc. etc.]
Remarquez que le compilateur Borland réserve les chaînes dans une zone
RW. Mais si on prend soin de commuter l'option "Merge duplicated
strings", le code suivant:
[segment de donnés initialisées] LC0: .ascii "Bonjour " (gcc optimise les chaînes dupliquées)
[prologue zappé] Dans le prologue, entr'autre, de la place est réservée sur la pile, et EBP positionné en conséquence. EBP ne sera plus modifié dans la fonction, et l'adressage par rapport à EBP accèdera aux données locales (et aux paramètres).
[char* ptr1 = "Bonjour";] movl $LC0, -4(%ebp) # ptr1 (gcc a réservé un pointeur dans la pile, en EBP - 4, et l'initialise par l'adresse de la chaîne dans le segment de données initialisées)
[char ptr2[] = "Bonjour";] movl LC0, %eax movl LC0+4, %edx movl %eax, -16(%ebp) # ptr2 movl %edx, -12(%ebp) # ptr2 (gcc a réservé le tableau dans la pile, de EBP - 16 à EBP - 9, et remplit cette zone par le contenu de LC0. L'utilisation de EAX/EDX est une "ruse" tenant compte du fait que "Bonjour " fait 8 char. Le cas général est une recopie par boucle)
[*ptr1 = 'A';] movl -4(%ebp), %eax # ptr1 movb $65, (%eax) (gcc copie dans EAX l'adresse de LC0 qui est en EBP - 4, puis modifie la mémoire pointée par EAX, donc en zone de données initialisées -> crash)
[*ptr2 = 'C';] movb $67, -16(%ebp) (gcc modifie la mémoire en EBP - 16, c'est à dire le premier élément de ptr2[], pas de problème)
[etc. etc.]
Remarquez que le compilateur Borland réserve les chaînes dans une zone RW. Mais si on prend soin de commuter l'option "Merge duplicated strings", le code suivant:
Le code assembleur me semble correct. Qu'est ce qui se passe ?
Donc où est le problème ? La réponse C a été donnée. Personnellement, je trouve zarbi que le
compilo puisse légalement initialiser une variable automatique pointeur non constant par la valeur d'un pointeur constant, mais c'est comme ça.
Pour le C++ cette complainte est correcte - mais une telle initialisation ou affectation y est permise pour des raisons historiques de compatibilité avec le C ;-) mais pour le C elle n'est pas correcte. En C "toto" est un tableau de char non constants et la conversion en pointeur donne un simple char* non constant.
La norme dit seulement que le comportement d'un essai de modifier un tel tableau est indéfini - bien qu'il s'agisse du point de vue syntactique d'un tableau de chars non constants.
-- Horst
Pierre Maurette <maurettepierre@wanadoo.fr> wrote:
Bonjour,
Je travaille avec gcc-3.3.3 sous fedora.
[...]
Le code assembleur me semble correct. Qu'est ce qui se passe ?
Donc où est le problème ?
La réponse C a été donnée. Personnellement, je trouve zarbi que le
compilo puisse légalement initialiser une variable automatique pointeur
non constant par la valeur d'un pointeur constant, mais c'est comme ça.
Pour le C++ cette complainte est correcte - mais une telle
initialisation ou affectation y est permise pour des raisons
historiques de compatibilité avec le C ;-) mais pour le C elle n'est
pas correcte. En C "toto" est un tableau de char non constants et la
conversion en pointeur donne un simple char* non constant.
La norme dit seulement que le comportement d'un essai de modifier un
tel tableau est indéfini - bien qu'il s'agisse du point de vue
syntactique d'un tableau de chars non constants.
Le code assembleur me semble correct. Qu'est ce qui se passe ?
Donc où est le problème ? La réponse C a été donnée. Personnellement, je trouve zarbi que le
compilo puisse légalement initialiser une variable automatique pointeur non constant par la valeur d'un pointeur constant, mais c'est comme ça.
Pour le C++ cette complainte est correcte - mais une telle initialisation ou affectation y est permise pour des raisons historiques de compatibilité avec le C ;-) mais pour le C elle n'est pas correcte. En C "toto" est un tableau de char non constants et la conversion en pointeur donne un simple char* non constant.
La norme dit seulement que le comportement d'un essai de modifier un tel tableau est indéfini - bien qu'il s'agisse du point de vue syntactique d'un tableau de chars non constants.