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

Fermé
cest_pas_faux - Modifié le 9 août 2019 à 11:52
mamiemando Messages postés 33079 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 23 avril 2024 - 12 août 2019 à 11:06
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

mamiemando Messages postés 33079 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 23 avril 2024 7 749
Modifié le 12 août 2019 à 11:11
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
Dalfab Messages postés 706 Date d'inscription dimanche 7 février 2016 Statut Membre Dernière intervention 2 novembre 2023 101
9 août 2019 à 23:51
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
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
Dalfab Messages postés 706 Date d'inscription dimanche 7 février 2016 Statut Membre Dernière intervention 2 novembre 2023 101
10 août 2019 à 10:48
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
c_est_pas_faux
10 août 2019 à 11:06
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
Dalfab Messages postés 706 Date d'inscription dimanche 7 février 2016 Statut Membre Dernière intervention 2 novembre 2023 101
10 août 2019 à 21:41
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