Bonjour.
Je remarque que ce sujet est tombé (et tombe toujours) à plusieurs reprises dans le forum.
Alors je crée ce topic, déjà pour me permettre les prochaines fois de répondre aux sujets en méttant directement le lien de celui ci, et puis aussi pour permettre aux débutants qui liront spontanément ce topic de saisir la différence entre char *, et char []
Segfault bizarre
La plupart des débutants se demandent pourquoi en faisant ceci :
int main (void)
{
char *tab="Salut";
tab[0]='s';
return 0;
}
ça segfault !!
En fait, en C, le fait d'initialiser un pointeur avec une chaine de caractère, ceci a pour effet de mettre la chaine de caractère en tant que variable constante.
Mais la question qui se pose (enfin, si vous vous la posez), est pourquoi ça segfault.
Et bien voyons ce que le compilateur compile en lui donnant ce code :
Ne vous affolez pas, je vais tout expliquer.
Remarquez qu'à la ligne 4, on trouve la déclaration de la chaine "Salut".
Juste avant, à la ligne 2, il y a une déclaration de zone mémoire (section). C'est la section rodata.
La section rodata est une section de variable constante. Cette section est protégé en écriture.
C'est pour cela que si on tente de la modifier, la mémoire déclenche une erreur, d'où le segfault.
Si vous voulez faire des tests pour vous amuser, changez .rodata en .data, compilez et exécutez.
Comme candide l'a fait remarquer un peu plus bas, pour avoir l'erreur du compilateur face à ce genre de pratique, il est préférable de déclarer le pointeur en tant que constant de cette façon :
const char *tab="Salut";
Ainsi, le code ne compilera même pas.
Impossible de changer de tableau
Maintenant, prenons cet exemple :
int main (void)
{
char tab[]="Salut";
char tab2[] = "Salut";
tab = tab2;
return 0;
}
Là, il n y a rien à comprendre, le langage C interdit ceci.
Histoire de différence
Une autre question peut se poser, est :
Quelle est la différence entre
char tab[] = "Salut";
Et
char *tab = "Salut";
Bon déjà on avait dit que la deuxième écriture est en lecture seulement.
Maintenant, que se passe t il pour la première ?
Voyons le code assembleur généré pour ce code ci :
Magie, il n'y a pas l'ombre de la chaine "Salut" !!!
Bizarre non ?
En fait non, ce n'est pas bizarre, les deux lignes 9 et 10 correspondent à la chaine "Salut".
Donc en fait, le fait de faire :
char tab[]="Salut";
Ne fait qu'initialiser le tableau tab comme si on l'avait fait à la main, à quelques exceptions près.
La différence entre initialiser un tab[] avec une chaine, et le faire à la main
Eh oui, 6 instructions processeur (de 9 à 14) pour initialiser le tableau.
Tandis qu'avant, le compilateur s'est arrangé pour le faire en seulement 2 instructions.
En fait ce que fait le compilo, c'est qu'au lieu de rentrer octet par octet, il fait par des blocs d'octet (c'est pour ça qu'avant y'avait des valeurs bizarres).
Bonus
Je ne sais pas trop si je dois vous le dire ou pas (au pire si les modos n'aiment pas cette partie, on la supprime), l'utilisation des chaines dans .rodata est LA technique qui permet de cracker les logiciels.
Je n'en dirais pas plus, mais juste pour vous mettre sur les rails, remarquez qu'avec char *tab = "Salut", la chaine "Salut" était en clair dans le fichier exécutable.
Bref, là franchement j'arrête de parler de ce sujet c'était juste un petit bonus.
Et ce n'est pas la peine de me poser des questions sur cette partie, car je n y répondrai pas.
Sinon j'essaierai d'alimenter ce topic par d'autres illustrations si besoin est.
Belles explications. Ton sujet me rappel que j'avais aussi essayé de faire le point sur les différences entre pointeur et tableau, mais sans entrer dans l'ASM. Comme il a été dit, très peu de débutants s'intéresseront à ce topic déjà de niveau avancé si je puis dire. J'espère quand même que certains le liront car cela évitera des erreurs inutiles qui font perdre énormément de temps.
déclare un pointeur -non initialisé- que l'on peut modifier, par exemple en faisant
pointeur = "abc";
// ou
char lettre;
pointeur = & lettre
// ou
char tableau[10];
pointeur = tableau;
// ou ...
pointeur = malloc(6421);
on peut le modifier, donc il n'est pas "en lecture seulement"
Par contre, il pointe vers un endroit, un bout de la mémoire, qui peut être modifiable ou pas. Par exemple, les chaines constantes - quelle surprise - ne sont normalement pas modifiables
char tableau[10];
pointeur = tableau; // vers tableau variable
*pointeur = 'w'; // met 'w' dans tableau[0], ok
pointeur = "abcd"; // vers chaine constante
*pointeur = 'A'; // crac boum badaboum
Tu es certain d'avoir tout lu ? Parce que les questions que tu poses trouvent une explication dans le post d'Arthurus, notamment dans la partie : Impossible de changer de tableau.
apprentiZero a écrit:
Pour moi en utilisant un char*, il faut allouer dynamiquement de la mémoire pour la chaine bah sinon ... Segfault directement, non ?
Non... La chaîne est seulement en lecture seule. Ce qui n'empèche pas changer l'adresse pointée.
Très bonne initiative, j'ai appris quelques trucs pratiques que je ne savais pas, notamment le coup du "const char*" qui fait crier le compilateur (ce qui me paraît logique après coup).
Sinon une question un peu à côté (mais pas tant), le code asm, tu as utilisé un désassembleur ou tu as une méthode de voir directement le code que sort le compilo dans ton ide (ou encore, une simple option dans gcc ?) ?
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
Objectif Zéro Bug - le test logiciel professionnel | L'électronique de zéro | Tableaux & pointeurs | Pointeurs sur fonctions | Lecture/écriture binaire