web-dev-qa-db-fra.com

"int * nums = {5, 2, 1, 4}" provoque une erreur de segmentation

int *nums = {5, 2, 1, 4};
printf("%d\n", nums[0]);

provoque un segfault, alors que

int nums[] = {5, 2, 1, 4};
printf("%d\n", nums[0]);

non. Maintenant:

int *nums = {5, 2, 1, 4};
printf("%d\n", nums);

impressions 5.

Sur cette base, j'ai supposé que la notation d'initialisation du tableau, {}, charge aveuglément ces données dans la variable de gauche. Lorsqu'il est int [], le tableau est rempli comme vous le souhaitez. Quand il est int *, le pointeur est rempli par 5, et les emplacements de mémoire après où le pointeur est stocké sont remplis par 2, 1 et 4. Donc nums [0] tente de déréférencer 5, provoquant une erreur de segmentation.

Si je me trompe, veuillez me corriger. Et si j'ai raison, veuillez élaborer, car je ne comprends pas pourquoi les initialiseurs de tableaux fonctionnent comme ils le font.

81
user1299784

Il y a une règle (stupide) en C disant que toute variable simple peut être initialisée avec une liste d'initialisation entre accolades, comme s'il s'agissait d'un tableau.

Par exemple, vous pouvez écrire int x = {0};, ce qui est complètement équivalent à int x = 0;.

Ainsi, lorsque vous écrivez int *nums = {5, 2, 1, 4}; vous donnez en fait une liste d'initialisation à une seule variable de pointeur. Cependant, il ne s'agit que d'une seule variable, de sorte que la première valeur ne lui sera attribuée que 5, le reste de la liste est ignoré (en fait, je ne pense pas que le code avec des initialiseurs en excès devrait même compiler avec un compilateur strict) - il ne le fait pas être écrit dans la mémoire du tout. Le code est équivalent à int *nums = 5;. Ce qui signifie que numsdoit pointer vers adresse5.

À ce stade, vous devriez déjà avoir obtenu deux avertissements/erreurs du compilateur:

  • Affectation d'un entier au pointeur sans transtypage.
  • Excès d'éléments dans la liste d'initialisation.

Et puis bien sûr le code va planter et graver depuis 5 n'est probablement pas une adresse valide avec laquelle vous pouvez déréférencer nums[0].

En guise de remarque, vous devez printf adresses de pointeur avec le %p spécificateur ou sinon vous invoquez un comportement indéfini.


Je ne sais pas trop ce que vous essayez de faire ici, mais si vous souhaitez définir un pointeur pour pointer sur un tableau, vous devez faire:

int nums[] = {5, 2, 1, 4};
int* ptr = nums;

// or equivalent:
int* ptr = (int[]){5, 2, 1, 4};

Ou si vous souhaitez créer un tableau de pointeurs:

int* ptr[] = { /* whatever makes sense here */ };

[~ # ~] modifier [~ # ~]

Après quelques recherches, je peux dire que la "liste d'initialisation des éléments en excès" n'est en effet pas valide C - c'est un extension GCC .

Le standard 6.7.9 Initialisation dit (accentuez le mien):

2 No initializer shall attempt to provide a value for an object not contained within the entity being initialized.

/ - /

11 L'initialiseur d'un scalaire doit être une expression unique, éventuellement entourée d'accolades. La valeur initiale de l'objet est celle de l'expression (après conversion) ; les mêmes contraintes de type et conversions que pour l'affectation simple s'appliquent, en prenant le type du scalaire comme la version non qualifiée de son type déclaré.

"Type scalaire" est un terme standard se référant à des variables uniques qui ne sont pas de type tableau, struct ou union (celles-ci sont appelées "type agrégé").

Donc, en anglais simple, la norme dit: "lorsque vous initialisez une variable, n'hésitez pas à ajouter des accolades supplémentaires autour de l'expression d'initialisation, juste parce que vous le pouvez."

113
Lundin

SCÉNARIO 1

int *nums = {5, 2, 1, 4};    // <-- assign multiple values to a pointer variable
printf("%d\n", nums[0]);    // segfault

Pourquoi est-ce un défaut de segmentation?

Vous avez déclaré nums comme pointeur vers int - c'est-à-dire que nums est censé contenir l'adresse n entier dans la mémoire.

Vous avez ensuite tenté d'initialiser nums dans un tableau de valeurs multiple. Donc, sans creuser beaucoup de détails, c'est conceptuellement incorrect - cela n'a pas de sens d'attribuer plusieurs valeurs à une variable qui est censée contenir une valeur. À cet égard, vous verriez exactement le même effet si vous faites cela:

int nums = {5, 2, 1, 4};    // <-- assign multiple values to an int variable
printf("%d\n", nums);    // also print 5

Dans les deux cas (attribuez plusieurs valeurs à un pointeur ou à une variable int), ce qui se produit alors, c'est que la variable obtiendra la première valeur qui est 5, tandis que les valeurs restantes sont ignorées. Ce code est conforme mais vous obtiendrez des avertissements pour chaque valeur supplémentaire qui n'est pas censée être dans l'affectation:

warning: excess elements in scalar initializer.

Dans le cas de l'attribution de plusieurs valeurs à une variable de pointeur, le programme segfaults lorsque vous accédez à nums[0], ce qui signifie que vous différez ce qui est stocké dans adresse 5 littéralement. Dans ce cas, vous n'avez alloué aucune mémoire valide pour le pointeur nums.

Il convient de noter qu'il n'y a pas de défaut de segmentation dans le cas de l'attribution de plusieurs valeurs à la variable int (vous ne déréférencez aucun pointeur non valide ici).


SCÉNARIO 2

int nums[] = {5, 2, 1, 4};

Celui-ci n'est pas défectueux, car vous allouez légalement un tableau de 4 pouces dans la pile.


SCÉNARIO

int *nums = {5, 2, 1, 4};
printf("%d\n", nums);   // print 5

Celui-ci ne se configure pas comme prévu, car vous imprimez la valeur du pointeur lui-même - PAS ce qu'il s'agit de déréférencer (ce qui est un accès mémoire non valide).


Autres

Il est presque toujours voué à la faute chaque fois que vous coder en dur la valeur d'un pointeur comme ceci (car c'est la tâche du système d'exploitation de déterminer quel processus peut accéder à quel emplacement de mémoire).

int *nums = 5;    // <-- segfault

Donc, une règle d'or consiste à toujours initialiser un pointeur sur l'adresse d'une variable allouée, telle que:

int a;
int *nums = &a;

ou,

int a[] = {5, 2, 1, 4};
int *nums = a; 
28
artm

int *nums = {5, 2, 1, 4}; est un code mal formé. Il existe une extension GCC qui traite ce code de la même manière que:

int *nums = (int *)5;

essayer de former un pointeur vers l'adresse mémoire 5. (Cela ne me semble pas être une extension utile, mais je suppose que la base de développeurs le veut).

Pour éviter ce comportement (ou au moins, obtenir un avertissement), vous pouvez compiler en mode standard, par ex. -std=c11 -pedantic.

Une autre forme de code valide serait:

int *nums = (int[]){5, 2, 1, 4};

qui pointe vers un littéral mutable de la même durée de stockage que nums. Cependant, le int nums[] la version est généralement meilleure car elle utilise moins de stockage, et vous pouvez utiliser sizeof pour détecter la longueur du tableau.

25
M.M
int *nums = {5, 2, 1, 4};

nums est un pointeur de type int. Vous devez donc faire ce point vers un emplacement de mémoire valide. num[0] vous essayez de déréférencer un emplacement de mémoire aléatoire et donc le défaut de segmentation.

Oui, le pointeur contient la valeur 5 et vous essayez de déréférencer ce qui est un comportement non défini sur votre système. (Ressemble à 5 n'est pas un emplacement mémoire valide sur votre système)

Tandis que

int nums[] = {1,2,3,4};

est une déclaration valide dans laquelle vous dites nums est un tableau de type int et la mémoire est allouée en fonction du nombre d'éléments transmis lors de l'initialisation.

12
Gopi

En affectant {5, 2, 1, 4}

int *nums = {5, 2, 1, 4};

vous affectez 5 à nums (après un transtypage implicite de int en pointeur vers int). Déréférer fait un appel d'accès à l'emplacement de mémoire à 0x5. Cela pourrait ne pas être autorisé à accéder à votre programme.

Essayer

printf("%p", (void *)nums);
10
Fahad Siddiqui