Partage
  • Partager sur Facebook
  • Partager sur Twitter

Différence entre char *tab et char tab[]

    14 avril 2010 à 23:53:23

    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 :
            .file        "toto.c"
            .section        .rodata
    .LC0:
            .string        "Salut"
            .text
    .globl main
            .type        main, @function
    main:
            pushl        %ebp
            movl        %esp, %ebp
            subl        $16, %esp
            movl        $.LC0, -4(%ebp)
            movl        -4(%ebp), %eax
            movb        $115, (%eax)
            movl        $0, %eax
            leave
            ret
            .size        main, .-main
            .ident        "GCC: (Ubuntu 4.4.1-4ubuntu9) 4.4.1"
            .section        .note.GNU-stack,"",@progbits


    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 :
    int main (void)
    {
    	char tab[]="Salut";
    	return 0;
    }
    


            .file        "toto.c"
            .text
    .globl main
            .type        main, @function
    main:
            pushl        %ebp
            movl        %esp, %ebp
            subl        $16, %esp
            movl        $1970037075, -6(%ebp)
            movw        $116, -2(%ebp)
            movl        $0, %eax
            leave
            ret
            .size        main, .-main
            .ident        "GCC: (Ubuntu 4.4.1-4ubuntu9) 4.4.1"
            .section        .note.GNU-stack,"",@progbits

    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


    Prenons cet exemple :
    int main (void)
    {
    	char tab[6];
    	tab[0] = 'S';
    	tab[1] = 'a';
    	tab[2] = 'l';
    	tab[3] = 'u';
    	tab[4] = 't';
    	tab[5] = '\0';
    	return 0;
    }
    


    Le code assembleur correspondant est :
            .file        "toto.c"
            .text
    .globl main
            .type        main, @function
    main:
            pushl        %ebp
            movl        %esp, %ebp
            subl        $16, %esp
            movb        $83, -6(%ebp)
            movb        $97, -5(%ebp)
            movb        $108, -4(%ebp)
            movb        $117, -3(%ebp)
            movb        $116, -2(%ebp)
            movb        $0, -1(%ebp)
            movl        $0, %eax
            leave
            ret
            .size        main, .-main
            .ident        "GCC: (Ubuntu 4.4.1-4ubuntu9) 4.4.1"
            .section        .note.GNU-stack,"",@progbits


    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.
    • Partager sur Facebook
    • Partager sur Twitter
      14 avril 2010 à 23:56:39

      Je met un lien vers ton post dans mon post de la FAQ. :)
      • Partager sur Facebook
      • Partager sur Twitter
        14 avril 2010 à 23:57:31

        Tres bonne initiative Arthurus.
        Je vais lire ça.
        • Partager sur Facebook
        • Partager sur Twitter
          Staff 15 avril 2010 à 0:10:12

          que c'est moche le code asm généré par les compilateurs ><
          Sinon c'est une bonne idée, mais je doute que ça éclaircisse l'esprit des débutants :s
          • Partager sur Facebook
          • Partager sur Twitter
            15 avril 2010 à 0:16:05

            Disons que c'est plus destiné aux débutants curieux ;)
            Un débutant qui n'en a rien à foutre, ne lira même pas mon post.

            @Pouet : j'avais pas vu ton post dans la FAQ, mais ça ne fait rien, ça lui fait une extension ;)
            @hibalum : Merci.
            • Partager sur Facebook
            • Partager sur Twitter
              Staff 15 avril 2010 à 0:17:38

              Ouais déjà qu'ils en ont rien à faire du cour >< lol
              • Partager sur Facebook
              • Partager sur Twitter
                15 avril 2010 à 0:31:00

                Soyons clair.
                Ce genre d'article aideront ceux qui le veulent. Les autre se noyerons tout seul. Tout effort n'est pas vain.

                Sinon, franchement Arthurus, j'adore trop ton bonus. Ca va me donner l'envie de me pencher sur l'assembleur.

                C'est une tres bonne initiative, je garde ca sous le coude
                • Partager sur Facebook
                • Partager sur Twitter
                  Staff 15 avril 2010 à 0:54:37

                  L'assembleur c'est génial *_* c'est pas si difficile qu'on le dit mais ça peut devenir vite un casse tête
                  • Partager sur Facebook
                  • Partager sur Twitter
                    15 avril 2010 à 1:07:06

                    Salut,

                    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.
                    • Partager sur Facebook
                    • Partager sur Twitter
                      15 avril 2010 à 15:15:01

                      Citation : Arthurus


                      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 !!



                      C'est l'occasion de rappeler ce que -ed- (et d'autres) défendait ardemment, à savoir déclarer "protectivement"

                      const char *tab="Salut";
                      


                      pour recevoir un avertissement du compilateur en cas d'écriture dans la zone statique interdite.

                      • Partager sur Facebook
                      • Partager sur Twitter
                        15 avril 2010 à 15:32:16

                        Merci.
                        Je le rajoute à mon post
                        • Partager sur Facebook
                        • Partager sur Twitter
                          5 août 2014 à 16:58:07

                          Arthurus ton Bonus est super ^^ !
                          • Partager sur Facebook
                          • Partager sur Twitter
                          lalala j'aime les pattes ...
                            5 août 2014 à 17:11:39

                            Bon, je viens de lire ce topic déterré et j'ai plusieurs questions:

                            En fait, ce que je retiens c'est qu'un char* est en lecture seulement mais finalement quelle est la différence entre un char* et un char[] ?

                            Par exemple, est ce qu'on peut faire

                            char *tab = "Salut";
                            char tab2[] = "Yep";
                            
                            tab2 = tab;
                            
                            // ou : 
                            
                            tab = tab2;

                            Pour moi en utilisant un char*, il faut allouer dynamiquement de la mémoire pour la chaine bah sinon ... Segfault directement, non ?

                            • Partager sur Facebook
                            • Partager sur Twitter
                              Staff 5 août 2014 à 17:17:25

                              Bonjour apprentiZero,

                              Ouvre un nouveau topic pour entamer une discussion seine et qui soit axée sur ton incompréhension à toi.

                              char[] est un type incomplet et char* est un pointeur.

                              Dans ta nouvelle discussion ajoute un lien vers celle-ci au pire.

                              • Partager sur Facebook
                              • Partager sur Twitter
                                5 août 2014 à 17:17:26

                                Pas tout à fait

                                char *pointeur;

                                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





                                -
                                Edité par michelbillaud 5 août 2014 à 17:21:36

                                • Partager sur Facebook
                                • Partager sur Twitter
                                  5 août 2014 à 17:27:17

                                  apprentiZero a écrit:

                                  Bon, je viens de lire ce topic déterré et j'ai plusieurs questions:

                                  En fait, ce que je retiens c'est qu'un char* est en lecture seulement mais finalement quelle est la différence entre un char* et un char[] ?

                                  Par exemple, est ce qu'on peut faire

                                  char *tab = "Salut";
                                  char tab2[] = "Yep";
                                  
                                  tab2 = tab;
                                  
                                  // ou : 
                                  
                                  tab = tab2;

                                  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.

                                  #include <stdio.h>
                                  
                                  int main(void){
                                  
                                     char const *p1 = "titi";
                                     char const *p2 = "toto";
                                  
                                     printf("%s %s\n", p1, p2);
                                     char const *tmp = p1;
                                     p1 = p2;
                                     p2 = tmp;
                                     printf("%s %s\n", p1, p2);
                                  
                                     return 0;
                                  }
                                  


                                  edit: pas vu les 2 posts précédents au moment de la rédaction de celui-ci.

                                  -
                                  Edité par GurneyH 5 août 2014 à 18:05:23

                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                  Zeste de Savoir, le site qui en a dans le citron !
                                    5 août 2014 à 18:14:43

                                    Merci pour votre explication, je crois que j'ai confondu chaine constante et pointeur (vers chaine) constant ...

                                    Je vais bidouiller un peu et je reviens si j'ai d'autres questions :D

                                    • Partager sur Facebook
                                    • Partager sur Twitter
                                      5 août 2014 à 21:19:48

                                      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 ?) ?

                                      Merci en tout cas !

                                      • Partager sur Facebook
                                      • Partager sur Twitter

                                      Différence entre char *tab et char tab[]

                                      × 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.
                                      • Editeur
                                      • Markdown