Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

différence entre "char *str" et "char str[]".

8 réponses
Avatar
Cedric Foll
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 ?
J'ai regardé la différence entre les deux codes assembleurs générés:
---------------
[follc@follc tmp]$ diff test*s
1c1
< .file "test2.c"
---
> .file "test.c"
15,19c15,17
< movl .LC0, %eax
< movl .LC0+4, %edx
< movl %eax, -8(%ebp)
< movl %edx, -4(%ebp)
< movb $65, -8(%ebp)
---
> movl $.LC0, -4(%ebp)
> movl -4(%ebp), %eax
> movb $65, (%eax)
-----------------

Le code assembleur de test.s en intégrale:
-------------------
$ cat test.s
.file "test.c"
.section .rodata
.LC0:
.string "Bonjour"
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
andl $-16, %esp
movl $0, %eax
subl %eax, %esp
movl $.LC0, -4(%ebp)
movl -4(%ebp), %eax
movb $65, (%eax)
movl $0, %eax
leave
ret
.size main, .-main
.section .note.GNU-stack,"",@progbits
.ident "GCC: (GNU) 3.3.3 20040412 (Red Hat Linux 3.3.3-7)"
-----------------------

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 ?

Donc où est le problème ?

8 réponses

Avatar
Eric Lévénez
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.

Avatar
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.

Avatar
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

Avatar
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):

int main(){
char* ptr1 = "Bonjour";
char ptr2[] = "Bonjour";
*ptr1 = 'A';
*ptr2 = 'C';
printf("%s .... %sn", ptr1, ptr2);
return 0;
}

Dans le listing asm, on trouve:

[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:

int main(){
char* ptr1 = "Bonjour";
char ptr2[] = "Bonjour";
char* ptr3 = "Bonjour";
*ptr1 = 'A';
*ptr2 = 'C';
printf("%s .... %s ... %sn", ptr1, ptr2, ptr3);
return 0;
}

produit:
Aonjour .... Conjour ... Aonjour

--
Pierre

Avatar
FAb
Pierre Maurette writes:


int main(){
char* ptr1 = "Bonjour";
char ptr2[] = "Bonjour";
char* ptr3 = "Bonjour";
*ptr1 = 'A';
*ptr2 = 'C';
printf("%s .... %s ... %sn", ptr1, ptr2, ptr3);
return 0;
}

produit:
Aonjour .... Conjour ... Aonjour


$ gcc -O3 -Wwrite-strings titi.c
titi.c: In function `main':
titi.c:2: warning: initialization discards qualifiers from pointer target type
titi.c:4: warning: initialization discards qualifiers from pointer target type
$ gcc -O3 -Wwrite-strings -fwritable-strings titi.c
$ ./titi
Segmentation fault
$ gcc --version
gcc (GCC) 3.3.3 20040412 (Red Hat Linux 3.3.3-7)

FAb

Avatar
FAb
Pierre Maurette writes:


int main(){
char* ptr1 = "Bonjour";
char ptr2[] = "Bonjour";
char* ptr3 = "Bonjour";
*ptr1 = 'A';
*ptr2 = 'C';
printf("%s .... %s ... %sn", ptr1, ptr2, ptr3);
return 0;
}

produit:
Aonjour .... Conjour ... Aonjour


$ gcc -O3 -Wwrite-strings titi.c
titi.c: In function `main':
titi.c:2: warning: initialization discards qualifiers from pointer target type
titi.c:4: warning: initialization discards qualifiers from pointer target type
$ gcc -O3 -Wwrite-strings -fwritable-strings titi.c
$ ./titi
Segmentation fault
$ gcc --version
gcc (GCC) 3.3.3 20040412 (Red Hat Linux 3.3.3-7)

FAb

Avatar
Cedric Foll
Merci à tous pour vos réponses.
Avatar
Horst Kraemer
Pierre Maurette 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.

--
Horst