Déclaration de tableau : *tab = {1, 2}

cest_pas_faux -  
mamiemando Messages postés 33228 Date d'inscription   Statut Modérateur Dernière intervention   -
Bonjour à tous,

Pourquoi ça segfault quand on fait :
int main ()
{
    int *a = {1, 2};

    for(int i = 0; i < 2; i++)
    {
        printf("%d\n", *(a + i));
    }
  
  return 0;
}


Et pourquoi ça segfault pas quand on fait :
int main ()
{
    int a[] = {1, 2};

    for(int i = 0; i < 2; i++)
    {
        printf("%d\n", *(a + i));
    }
  
  return 0;
}


Merci

2 réponses

  1. mamiemando Messages postés 33228 Date d'inscription   Statut Modérateur Dernière intervention   7 940
     
    Bonjour

    Explication du problème

    En fait il y a une différence entre déclarer ton tableau comme étant de type
    int[]
    ou
    int *


    Avec
    int[] t1 = {1, 2}
    , tout se passe "comme tu le crois". Le bloc
    {1,2}
    est créé en mémoire, et on prend l'adresse qui correspond au début de ce bloc pour l'assigner à
    t1
    . On n'a donc pas initialisé
    t1
    avec le bloc
    {1, 2}
    mais avec son adresse. Par la suite,
    t1
    peut être casté en
    int *
    car c'est une adresse, et donc on ne fait que changer le type du pointeur de
    int[]</code c> vers <code>int *
    .

    Avec
    int * t2 = {3, 4}
    , il n'en est rien. On initialise un pointeur au sens strict, donc il faut qu'à droite on ait un pointeur. Quand le type assigné à un pointeur n'est pas un pointeur, alors seuls les 32 premiers bits sont pris en compte. Tout se passe donc comme si
    {3, 4}
    était casté en
    int32
    . Comme le type de {3, 4} n'a pas été déclaré (tel que
    t1
    ), le compilateur lui donne par défaut le type
    int32[]
    . Donc ici,
    t2
    sera initialisé avec la première "case", donc à
    3
    .

    Et c'est ce que montre ce petit programme :

    #include <stdio.h>
    
    int main() {
        printf("sizeof(int) = %zu sizeof(int *) = %zu\n", sizeof(int), sizeof(int *));
        int t1[] = {1, 2};
        printf("t1 = %x\n", t1);
        int * t2 = {3, 4};
        printf("t2 = %x\n", t2);
        return 0;
    }


    ... pour lequel la compilation renvoie :

    toto.c: In function ‘main’:
    toto.c:7:17: warning: initialization of ‘int *’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
    int * t2 = {3, 4};
    ^
    toto.c:7:17: note: (near initialization for ‘t2’)
    toto.c:7:20: warning: excess elements in scalar initializer
    int * t2 = {3, 4};
    ^
    toto.c:7:20: note: (near initialization for ‘t2’)


    ... et dont le résultat est :

    sizeof(int) = 4 sizeof(int *) = 8
    t1 = 4df4baf0
    t2 = 3


    Ici on voit en particulier que
    t2
    est initialisé avec la copie bit à bit de
    {3, 4}
    qui sera vu comme un
    int
    , puisque
    {3, 4}
    n'est pas un pointeur. Comme c'est assez cavalier,
    gcc
    a eu le bon goût de nous prévenir avec le message warning: initialization of ‘int *’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion]

    On pourrait se dire que comme t2 peut emmagasiner 8 octets et qu'un int ne fait que 4 octets, l'intégralité de
    {3, 4}
    peut être intégralement recopiée bit à bit dans
    t2
    . On initialiserait donc
    t2
    à
    3 * 8^4 + 4
    . Il n'en est rien seul la première case,
    3
    a été copiée. Ceci confirme que le bloc à droite de
    =
    a été casté en un scalaire (une valeur numérique si tu préfères) de type
    int32
    . On comprend alors ce que gcc voulait dire par warning: excess elements in scalar initializer.

    Conclusion
    • Quand tu alloues un tableau d'entiers avec
      malloc
      utilises
      int *
      .
    • Quand tu initialises un tableau d'entiers avec un bloc de données constant (e.g.
      {1, 2}
      ) utilises
      int[]
      .
    • Par la suite un pointeur obtenu par
      int[]
      et
      int *
      ont sémantiquement le même comportement, mais restent de type différents. Tu auras donc besoin de caster un
      int[]
      en
      int *
      si tu souhaites utiliser une fonction qui prend un
      int *
      en paramètre.


    Remarques complémentaires sur les tableaux et le pointeurs
    • Si tu es sensible à ce qui se passe en mémoire, évite le type
      int
      car sa taille effective dépend de l'architecture du système. Tu peux par exemple utiliser
      int32 *
      après avoir inclu
      #include<stdint.h>
      ). Typiquement dans une application réseau, où les tailles dans un paquet sont normalisés, tu dois savoir ce qui se passe au bit près.
    • Comme ni
      int[]
      ni
      int *
      ne portent la taille du tableau (ni dans le type, ni en mémoire), une fonction qui prend un tel pointeur en paramètre aura besoin du nombre de cases dans ton tableau.
    • On ne passe en général jamais un
      int[N]
      avec N une valeur entière en paramètre d'une fonction : cela recopie les N entiers en pile. C'est donc "coûteux" car ça engendre des recopies inutiles. De plus, on ne passe plus le tableau par pointeur mais par recopie. Donc si la fonction tente de modifier le tableau, ce changement n'aurait lieu que sur la recopie.
    • Afin d'éviter de devoir passer systématiquement la taille des chaînes aux fonctions de
      <string.h>
      la convention choisie en C a consisté à définir un caractère d'arrêt
      '\0'
      . Mais comme
      '\0'
      vaut
      0
      (voir table ascii), ce genre d'astuce ne peut pas être utilisé pour un tableau de valeur numérique.
    • Pendant qu'on est dans les chaînes, la syntaxe
      "toto"
      n'est pas équivalente
      {'t', 'o', 't', 'o', '\0'}
      . Dans la première écriture, chaque lettre correspond bien à un
      char
      soit 1 octet. Dans la seconde écriture,
      {'t', 'o', 't', 'o', '\0'}
      sera vu comme un
      int32[]
      par le compilateur. Si tu initialises une chaîne avec ça, tu auras donc le même problème qu'avec
      t2
      , qui sera alors initialisé à
      (int) 't'
      soit
      74
      (voir tableau ascii).
    • On peut tout à fait utiliser la syntaxe avec les guillemets pour initialiser un pointeur "étoilé" comme un
      char *
      (exemple :
      const char * s = "bonjour";
      ).


    Bonne chance
    1
  2. Dalfab Messages postés 638 Date d'inscription   Statut Membre Dernière intervention   102
     
    Bonjour,

    La question devrait plutôt être : pourquoi la ligne suivante compile ?
        int *a = {1, 2};

    Cette ligne n'a aucun sens. La syntaxe {1,2} n'a sens que pour initialiser un tableau ou une structure. Mais ici il n'y a ni tableau ni structure!
    Le compilateur doit émettre un gros warning et la traiter comme s'il avait vu :
        int *a = 2;
    0
    1. c_est_pas_faux
       
      Bonjour,

      Mon but à la base était de parcourir un tableau de int* comme un tableau de char* :

      #include <stdio.h>

      int main ()
      {
      char a[] = {'a', 'b'};

      for(int i = 0; i < 2; i++)
      {
      printf("%c\n", *(a + i));
      }

      return 0;
      }



      Et ici, pareil, quand je faisais :

      char *a = {'a', 'b'};

      Il me crachait un segfault. Il n'y a pas une histoire comme quoi l'un est en lecture seule et l'autre expression est en lecture écriture ?
      0
    2. Dalfab Messages postés 638 Date d'inscription   Statut Membre Dernière intervention   102
       
      Il n'y a pas de différence d'utilisation pour
      char 
      et
      int
      . Sauf qu'il existe une notation pratique pour un table de char souvent appelée "chaîne de caractères'.
      Dans tous les cas,
      char*a
      ne crée pas un tableau, il peut pointer dessus. Pour
      int*a
      , même réponse.
      char a[] = {'a','b','c','\0'};// ok, a est un tableau
      char b[] = "abc";             // ok, notation chaîne pour un tableau de char
      char *c = "abc";              // toléré car "abc" est un tableau de 4 char constants et c pointe dessus
      const char *d = "abc";        // Ok, d pointe sur un tableau constant, notation possible
      char *e = {'a','b','c','\0'}; // aucun sens
      char *f = a;                  // ok, pointe sur un tableau
      char g[] = a;                 // interdit, copie de tableaux
      
      int h[] = {1,2,3,4};     // ok, h est un tableau
      int i[] = "abc";         // aucun sens (tableau d'int et tableau de const char)
      int *j = {1,2,3,4};      // aucun sens
      int *k = h;              // ok, pointe sur un tableau
      int l[] = h;             // interdit, copie de tableaux
      0
    3. c_est_pas_faux
       
      merci pour cette info.
      mais concernant les écritures :

      char *a = {'a','b','c','\0'};

      et

      char *a = "abc";

      Cela veut dire que déclarer un tableau avec les { } et les " " ne sont pas équivalentes ?
      Qu'est ce qui diffère ?
      0
    4. Dalfab Messages postés 638 Date d'inscription   Statut Membre Dernière intervention   102
       
      Les accolades sont des accolades et les guillemets sont des guillemets, donc ça n'est pas équivalent.

      Et comme je l'ai écris dans tes 2 expressions a est un pointeur car l'étoile est une étoile. On la voit bien les 2 fois.
      Ce qu'il faut savoir c'est que "abc" est un tableau de 4 caractères constants.
      La seule exception comme écrit avant est que la ligne suivante est acceptée. C'est elle l'exception qui n'est pas logique.
      char b[] = "abcde";           // ok, notation chaîne pour un tableau de char

      On n'a normalement pas le droit d'écrire tableau=tableau, mais il y a exception ici. C'est compris comme : recopie le tableau "abcde" pour initialiser le tableau b (taille par défaut = 5+1).
      char a[4] = "abcde";    // erreur
      char b[5] = "abcde";    // ok (mais erreur en C++)
      char c[6] = "abcde";    // ok vaut "abcde\0"
      char d[9] = "abcde";    // ok vaut "abcde\0\0\0\0"
      char e[] = "abcde";     // ok vaut "abcde\0"
      const char* f = "abcde";// f est un POINTEUR
      
      printf("%d", sizeof(e));      // => 6
      printf("%d", sizeof("abcde"));// => 6
      printf("%d", sizeof(d));      // => 9
      printf("%d", sizeof(f));      // => 4 ou 8 (c'est un pointeur)
      0