Pourquoi cela pour la sortie de boucle sur certaines plateformes et pas sur d'autres?

j'ai récemment commencé à apprendre C et je prends un cours avec C comme sujet. Je suis actuellement en train de jouer avec loops et je suis en train de rencontrer un comportement étrange que je ne sais pas comment expliquer.

#include <stdio.h>

int main()
{
  int array[10],i;

  for (i = 0; i <=10 ; i++)
  {
    array[i]=0; /*code should never terminate*/
    printf("test \n");

  }
  printf("%d \n", sizeof(array)/sizeof(int));
  return 0;
}

sur mon ordinateur portable avec Ubuntu 14.04, ce code ne casse pas. Il s'exécute à la fin. Sur L'ordinateur de mon école fonctionnant avec CentOS 6.6, il fonctionne aussi bien. Sous Windows 8.1, la boucle ne se termine jamais.

ce qui est encore plus étrange est-ce que lorsque je modifie la condition de la boucle for à: i <= 11 , le code se termine seulement sur mon ordinateur portable courant Ubuntu. Il ne se termine jamais dans les CentOS et les fenêtres.

est-ce que quelqu'un peut expliquer ce qui se passe dans la mémoire et pourquoi les différents os qui exécutent le même code donnent des résultats différents?

EDIT: je sais que pour la boucle sort des limites du terrain. Je suis en train de faire intentionnellement. Je ne peux pas comprendre comment le comportement peut être différent dans différents os et ordinateurs.

239
demandé sur Ziezi 2015-06-24 05:34:05
la source

14 ответов

sur mon ordinateur portable en cours D'Ubuntu 14.04, ce code ne casse pas il court à l'achèvement. Sur L'ordinateur de mon école fonctionnant avec CentOS 6.6, il fonctionne aussi bien. Sous Windows 8.1, la boucle ne se termine jamais.

ce qui est plus étrange , c'est que je modifie le conditionnel de la boucle for vers: i <= 11 , le code ne se termine que sur mon ordinateur portable tournant Ubuntu. CentOS et Windows ne se terminent jamais.

vous venez de découverte de la mémoire de piétinement. Vous pouvez en lire plus à ce sujet ici: qu'est Ce qu'un "mémoire stomp"?

quand vous attribuez int array[10],i; , ces variables vont dans la mémoire (spécifiquement, elles sont attribuées sur la pile, qui est un bloc de mémoire associé à la fonction). array[] et i sont probablement adjacents l'un à l'autre dans la mémoire. Il semble que sur Windows 8.1, i se trouve à array[10] . Sur CentOS, i est situé à array[11] . Et sur Ubuntu, il n'est ni à L'un ni à l'autre (peut-être au array[-1] ?).

essayez d'ajouter ces instructions de débogage à votre code. Vous devriez noter que sur l'itération 10 ou 11, array[i] points à i .

#include <stdio.h>

int main() 
{ 
  int array[10],i; 

  printf ("array: %p, &i: %p\n", array, &i); 
  printf ("i is offset %d from array\n", &i - array);

  for (i = 0; i <=11 ; i++) 
  { 
    printf ("%d: Writing 0 to address %p\n", i, &array[i]); 
    array[i]=0; /*code should never terminate*/ 
  } 
  return 0; 
} 
355
répondu QuestionC 2017-05-23 15:10:05
la source

le bug se trouve entre ces morceaux de code:

int array[10],i;

for (i = 0; i <=10 ; i++)

array[i]=0;

depuis array n'a que 10 éléments, dans la dernière itération array[10] = 0; est un débordement de tampon. Les dépassements de tampon sont comportement non défini , ce qui signifie qu'ils pourraient formater votre disque dur ou provoquer des démons à sortir de votre nez.

il est assez courant que toutes les variables de la pile soient disposées l'une à côté de l'autre. Si i est situé où array[10] écrit à, puis L'UB réinitialisera i à 0 , conduisant ainsi à la boucle non interrompue.

pour fixer, changer l'état de la boucle en i < 10 .

98
répondu o11c 2015-06-24 05:38:19
la source

dans ce qui devrait être la dernière exécution de la boucle,vous écrivez à array[10] , mais il n'y a que 10 éléments dans le tableau, numérotés de 0 à 9. La spécification du langage C indique qu'il s'agit d'un"comportement non défini". Ce que cela signifie dans la pratique est que votre programme tentera d'écrire à la int morceau de la taille de la mémoire qui se trouve immédiatement après array dans la mémoire. Ce qui se passe alors dépend de ce qui se trouve, en fait, là, et cela dépend non seulement du système d'exploitation mais plus encore sur le compilateur, sur les options du compilateur (comme les paramètres d'optimisation), sur l'architecture du processeur, sur le code environnant, etc. Il peut même varier d'une exécution à l'autre, par exemple en raison de "address space randomization (probablement pas sur cet exemple de jouet, mais il se produit dans la vie réelle). Quelques possibilités:

  • l'emplacement n'a pas été utilisé. La boucle se termine normalement.
  • le l'emplacement a été utilisé pour quelque chose qui s'est avéré avoir la valeur 0. La boucle se termine normalement.
  • l'emplacement contenait l'adresse de retour de la fonction. La boucle se termine normalement, mais le programme s'écrase parce qu'il essaie de passer à l'adresse 0.
  • l'emplacement contient la variable i . La boucle ne se termine jamais parce que i redémarre à 0.
  • L'emplacement contient une autre variable. Le la boucle se termine normalement, mais ensuite des choses" intéressantes " se produisent.
  • l'emplacement est une adresse mémoire invalide, par exemple parce que array se trouve juste à la fin d'une page mémoire virtuelle et que la page suivante n'est pas mappée.
  • des démons sortent de votre nez . Heureusement, la plupart des ordinateurs ne disposent pas du matériel nécessaire.

ce que vous avez observé sur Windows est que le compilateur a décidé de placer le la variable i immédiatement après le tableau en mémoire, donc array[10] = 0 finit par attribuer à i . Sur Ubuntu et CentOS, le compilateur n'y a pas placé i . Presque toutes les implémentations C groupent des variables locales en mémoire, sur une pile de mémoire , avec une exception majeure: certaines variables locales peuvent être entièrement placées dans registres . Même si la variable est sur la pile, l'ordre des variables est déterminée par la compilateur, et cela peut dépendre non seulement de l'ordre dans le fichier source, mais aussi de leurs types (pour éviter de gaspiller la mémoire à des contraintes d'alignement qui laisseraient des trous), de leurs noms, de certaines valeurs de hachage utilisées dans la structure interne des données d'un compilateur, etc.

Si vous voulez savoir ce que votre compilateur a décidé de faire, vous pouvez dire à vous montrer le code assembleur. Oh, et apprendre à déchiffrer assembleur (c'est plus facile que de l'écrire). Avec GCC (et quelques autres compilateurs), surtout dans le monde Unix), passez l'option -S pour produire du code assembleur au lieu d'un binaire. Par exemple, voici l'extrait du monteur pour la boucle de compilation avec GCC sur amd64 avec l'option d'optimisation -O0 (pas d'optimisation), avec des commentaires ajoutés manuellement:

.L3:
    movl    -52(%rbp), %eax           ; load i to register eax
    cltq
    movl    "151900920", -48(%rbp,%rax,4)      ; set array[i] to 0
    movl    $.LC0, %edi
    call    puts                      ; printf of a constant string was optimized to puts
    addl    , -52(%rbp)             ; add 1 to i
.L2:
    cmpl    , -52(%rbp)            ; compare i to 10
    jle     .L3

ici la variable i est de 52 octets sous le haut de la pile, tandis que le tableau commence 48 octets sous le haut de la pile. Il se trouve que ce compilateur a placé i juste avant le tableau; vous écririez i si vous écrivez à array[-1] . Si vous changez array[i]=0 en array[9-i]=0 , vous obtiendrez une boucle infinie sur cette plateforme particulière avec ces options de compilateur particulières.

compilons maintenant votre programme avec gcc -O1 .

    movl    , %ebx
.L3:
    movl    $.LC0, %edi
    call    puts
    subl    , %ebx
    jne     .L3

c'est plus court! Le compilateur n'a pas seulement refusé d'allouer un emplacement de pile pour i - c'est seulement jamais stocké dans le registre ebx - mais il n'a pas pris la peine d'allouer une mémoire pour array , ou de générer du code pour définir ses éléments, parce qu'il a remarqué qu'aucun des éléments ne sont jamais utilisés.

pour rendre cet exemple plus parlant, assurons-nous que les assignations de tableaux sont effectuées en fournissant au compilateur quelque chose qu'il n'est pas capable d'optimiser à l'extérieur. Une façon facile de faire cela est d'utiliser le tableau d'un autre fichier-en raison de la compilation séparée, le compilateur ne sait pas ce qui se passe dans un autre fichier (à moins qu'il optimise au moment du lien, ce que gcc -O0 ou gcc -O1 ne fait pas). Créer un fichier source use_array.c contenant

void use_array(int *array) {}

et changez votre code source en

#include <stdio.h>
void use_array(int *array);

int main()
{
  int array[10],i;

  for (i = 0; i <=10 ; i++)
  {
    array[i]=0; /*code should never terminate*/
    printf("test \n");

  }
  printf("%zd \n", sizeof(array)/sizeof(int));
  use_array(array);
  return 0;
}

compiler avec

gcc -c use_array.c
gcc -O1 -S -o with_use_array1.c with_use_array.c use_array.o

cette fois le code assembleur ressemble à ceci:

    movq    %rsp, %rbx
    leaq    44(%rsp), %rbp
.L3:
    movl    "151950920", (%rbx)
    movl    $.LC0, %edi
    call    puts
    addq    , %rbx
    cmpq    %rbp, %rbx
    jne     .L3

maintenant le tableau est sur la pile, 44 bytes de haut. Et i ? Il n'apparaît pas n'importe où! Mais le compteur de boucle est conservé dans le registre rbx . Ce n'est pas exactement i , mais l'adresse du array[i] . Le compilateur a décidé que puisque la valeur de i n'a jamais été utilisée directement, il n'y avait aucun intérêt à effectuer l'arithmétique pour calculer où stocker 0 pendant chaque passage de la boucle. Au lieu de cela, cette adresse est la variable de boucle, et l'arithmétique pour déterminer les limites a été effectuée en partie au moment de la compilation (multiplier 11 itérations par 4 octets par élément du tableau 44) et en partie au moment de l'exécution, mais une fois pour toutes avant la boucle commence (effectuer une soustraction pour obtenir la valeur initiale).

même sur cet exemple très simple, nous avons vu comment changer les options du compilateur (activer l'optimisation) ou changer quelque chose de mineur ( array[i] en array[9-i] ) ou même changer quelque chose apparemment sans rapport (en ajoutant l'appel à use_array ) peut faire un important différence à ce que le programme exécutable généré par le compilateur. les optimisations des compilateurs peuvent faire beaucoup de choses qui peuvent sembler peu intuitives sur les programmes qui invoquent un comportement non défini . C'est pourquoi un comportement indéfini est laissé complètement indéfini. Lorsque vous déviez si légèrement des pistes, dans les programmes du monde réel, il peut être très difficile de comprendre la relation entre ce que fait le code et ce qu'il aurait dû faire, même pour les programmeurs expérimentés.

38
répondu Gilles 2015-06-24 18:22:32
la source

contrairement à Java, C ne fait pas la vérification des limites du tableau, I. e, il n'y a pas de ArrayIndexOutOfBoundsException , la tâche de s'assurer que l'index du tableau est valide est laissée au programmeur. Faire cela volontairement mène à un comportement indéfini, tout peut arriver.


Pour un tableau:

int array[10]
Les indices

ne sont valables que dans l'intervalle 0 à 9 . Cependant, vous essayez de:

for (i = 0; i <=10 ; i++)

accès array[10] ici, changer la condition en i < 10

25
répondu Yu Hao 2015-06-24 06:14:48
la source

vous avez une violation des limites, et sur les plates-formes non terminantes, je crois que vous mettez par inadvertance i à zéro à la fin de la boucle, de sorte qu'elle recommence.

array[10] est invalide; il contient 10 éléments, array[0] à array[9] , et array[10] est le 11. Votre boucle doit être écrite pour arrêter avant 10 , comme suit:

for (i = 0; i < 10; i++)

array[10] terres est mise en œuvre définis, et de manière amusante, sur les deux plates-formes, il atterrit sur i , ces plates-formes apparemment poser directement après array . i est mis à zéro et la boucle continue pour toujours. Pour vos autres plateformes, i peut être situé avant array , ou array peut avoir un rembourrage après elle.

19
répondu Derek T. Jones 2015-06-24 07:06:51
la source

vous déclarez int array[10] signifie array a l'index 0 à 9 (total 10 éléments entiers qu'il peut contenir). Mais la boucle suivante,

for (i = 0; i <=10 ; i++)

boucle 0 à 10 signifie 11 temps. Par conséquent, quand i = 10 il va déborder le tampon et causer comportement non défini .

alors essayez ceci:

for (i = 0; i < 10 ; i++)

ou,

for (i = 0; i <= 9 ; i++)
12
répondu rakeb.mazharul 2015-08-03 06:53:15
la source

il n'est pas défini à array[10] , et donne comportement non défini comme décrit ci-dessus. Penser comme ceci:

j'ai 10 produits dans mon panier. Ce sont:

0: une boîte de céréales

1: pain

2: lait

3: Tarte

4: œufs

5: gâteau

6: 2 litres de soude

7: salade

8: hamburgers

9: crème glacée

cart[10] n'est pas défini, et peut donner une exception hors limites dans certains compilateurs. Mais apparemment, beaucoup ne le font pas. Le 11ème article apparent est un article qui n'est pas réellement dans le panier. le 11ème point pointe vers, ce que je vais appeler, un " point poltergeist."Elle n'a jamais existé, mais elle était là.

pourquoi certains compilateurs donnent i un index de array[10] ou array[11] ou même array[-1] est en raison de votre déclaration d'initialisation/déclaration. Certains compilateurs interprètent cela comme:

  • "attribuer 10 blocs de int s pour array[10] et un autre int bloc. pour le rendre plus facile, mettre à côté de l'autre."
  • comme avant, mais déplacez-le a espace ou deux loin, de sorte que array[10] ne pointe pas à i .
  • faire la même chose qu'avant, mais allouer i à array[-1] (parce qu'un index d'un tableau ne peut pas, ou ne devrait pas, être négatif), ou allouer à un endroit complètement différent parce que L'OS peut le gérer, et c'est plus sûr.

certains compilateurs veulent que les choses aillent plus vite, et certains compilateurs préfèrent la sécurité. Tout est une question de contexte. Si je développais une application pour l'ancien OS BREW (L'OS d'un téléphone de base), par exemple, il ne se soucierait pas de la sécurité. Si je développais pour un iPhone 6, alors il pourrait fonctionner rapidement quoi qu'il arrive, donc j'aurais besoin d'un accent sur la sécurité. (Sérieusement, avez-vous lu les directives de L'App Store D'Apple, ou lire sur le développement de Swift et Swift 2.0?)

7
répondu DDPWNAGE 2015-07-21 22:12:30
la source

puisque vous avez créé un tableau de taille 10, pour la condition de boucle devrait être comme suit:

int array[10],i;

for (i = 0; i <10 ; i++)
{

Actuellement, vous essayez d'accéder à l'emplacement non assigné de la mémoire en utilisant array[10] et il provoque le comportement non défini . Un behavior Non défini signifie que votre programme se comportera de façon indéterminée, de sorte qu'il peut donner des sorties différentes dans chaque exécution.

6
répondu Steephen 2015-06-24 05:38:20
la source

Eh bien, le compilateur C ne vérifie Habituellement pas les limites. Vous pouvez obtenir un défaut de segmentation dans le cas où vous vous référez à un endroit qui ne "appartient" pas à votre processus. Cependant, les variables locales sont attribuées sur la pile et selon la façon dont la mémoire est attribuée, la zone juste au-delà du tableau ( array[10] ) peut appartenir au segment de mémoire du processus. Ainsi, aucune faille de segmentation piège est jeté et c'est ce que vous semblez éprouver. Comme d'autres l'ont souligné, c'est un comportement non défini dans C et votre code peut être considéré comme erratique. Puisque vous êtes d'apprentissage C, vous êtes mieux de prendre l'habitude de vérifier les limites dans votre code.

5
répondu unxnut 2015-06-24 05:48:08
la source

au-delà de la possibilité que la mémoire puisse être aménagée de sorte qu'une tentative d'écrire à a[10] écrive effectivement i , il serait également possible qu'un compilateur d'optimisation puisse déterminer que le test de boucle ne peut pas être atteint avec une valeur de i supérieure à dix sans que le code ait d'abord accédé à l'élément de tableau inexistant a[10] .

Puisqu'une tentative d'accéder à cet élément serait un comportement non défini, le compilateur serait n'ont aucune obligation quant à ce que le programme pourrait faire après ce point. Plus précisément, puisque le compilateur n'aurait aucune obligation de générer du code pour vérifier l'index de boucle dans tous les cas où il pourrait être supérieur à dix, il n'aurait aucune obligation de générer du code pour le vérifier du tout; il pourrait plutôt supposer que le test <=10 donnera toujours vrai. Notez que cela serait vrai même si le code lu a[10] plutôt que de l'écrire.

4
répondu supercat 2015-06-26 22:00:33
la source

lorsque vous itérez après i==9 , vous assignez zéro aux" éléments du tableau "qui sont en fait localisés après le tableau , donc vous écrasez d'autres données. Très probablement vous écrasez la variable i , qui est situé après a[] . De cette façon, vous pouvez tout simplement réinitialiser la variable i à zéro et ainsi redémarrer la boucle.

Vous pourriez découvrir que vous-même si vous avez imprimé i la boucle:

      printf("test i=%d\n", i);

au lieu de juste

      printf("test \n");

bien sûr que le résultat dépend fortement de l'allocation de mémoire pour vos variables, qui à son tour dépend d'un compilateur et de ses paramètres, il est donc généralement comportement non défini - c'est pourquoi les résultats sur différentes machines ou différents systèmes d'exploitation ou sur différents compilateurs peuvent différer.

3
répondu CiaPan 2015-06-25 07:56:55
la source

l'erreur est dans la partie de tableau[10] w/c est également l'adresse de i (int tableau[10],i;). quand le tableau[10] est mis à 0 alors le i serait 0 w / C réinitialise la boucle entière et les causes de la boucle infinie. il y aura boucle infinie si array[10] est entre 0-10.la boucle correcte devrait être pour (i = 0; i <10 ; i++) {...} tableau int[10], i; pour (i = 0; i < = 10 ; i++) tableau[i]=0;

0
répondu Jonelle H. Castaneda 2015-07-21 18:57:02
la source

je vais suggérer quelque chose que je ne trouve pas ci-dessus:

Essayez d'assigner tableau[i] = 20;

je suppose que cela devrait supprimer le code partout.. (étant donné que vous gardez i< =10 ou ll)

si cela fonctionne, vous pouvez fermement décider que les réponses indiquées ici sont déjà correctes [la réponse liée à la mémoire piétiner un pour exemple.]

0
répondu Raining fire 2015-07-21 21:45:21
la source

il y a deux choses qui ne vont pas ici. L'int i est en fait un élément de Tableau, Tableau [10], comme vu sur la pile. Parce que vous avez permis à l'indexation de faire réellement le tableau[10] = 0, l'index de boucle, i, ne dépassera jamais 10. Disons for(i=0; i<10; i+=1) .

i++ est, comme K&R pourrait l'appeler, "mauvais genre". Il incrémente i par la taille de i, pas 1. i++ est pour les mathématiques pointées et i+=1 pour l'algèbre. Tout cela dépend du compilateur, il n'est pas un bon convention de transférabilité.

-9
répondu SkipBerne 2015-06-26 21:58:41
la source