Warum wird der Loop auf einigen Plattformen beendet und auf anderen nicht?

240

Ich habe vor kurzem angefangen, C zu lernen und nehme eine Klasse mit C als Fach. Ich spiele gerade mit Loops herum und stoße auf ein merkwürdiges Verhalten, das ich nicht erklären kann.

#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;
}

Auf meinem Laptop mit Ubuntu 14.04 bricht dieser Code nicht. Es läuft bis zur Fertigstellung. Auf dem Computer meiner Schule mit CentOS 6.6 läuft es auch einwandfrei. Unter Windows 8.1 wird die Schleife nie beendet.

Noch seltsamer ist, dass der Code nur auf meinem Laptop mit Ubuntu endet , wenn ich den Zustand der forSchleife auf: bearbeite i <= 11. Es wird niemals in CentOS und Windows beendet.

Kann jemand erklären, was im Speicher passiert und warum die verschiedenen Betriebssysteme, auf denen derselbe Code ausgeführt wird, unterschiedliche Ergebnisse liefern?

EDIT: Ich weiß, dass die for-Schleife außerhalb der Grenzen liegt. Ich mache es absichtlich. Ich kann einfach nicht herausfinden, wie unterschiedlich das Verhalten zwischen verschiedenen Betriebssystemen und Computern sein kann.

JonCav
quelle
147
Da Sie das Array überlaufen, tritt ein undefiniertes Verhalten auf. Undefiniertes Verhalten bedeutet, dass alles passieren kann, auch wenn es zu funktionieren scheint. Daher ist "Code sollte niemals enden" keine gültige Erwartung.
Kaylum
37
Genau, willkommen bei C. Ihr Array hat 10 Elemente - nummeriert von 0 bis 9.
Yetti99
14
@ JonCav Du hast den Code gebrochen. Sie erhalten ein undefiniertes Verhalten, bei dem es sich um fehlerhaften Code handelt.
Kaylum
50
Nun, der springende Punkt ist, dass undefiniertes Verhalten genau das ist. Sie können es nicht zuverlässig testen und beweisen, dass etwas Definiertes passieren wird. Was wahrscheinlich auf Ihrem Windows-Computer vor sich geht, ist, dass die Variable idirekt nach dem Ende von gespeichert arraywird und Sie sie mit überschreiben array[10]=0;. Dies ist möglicherweise nicht der Fall bei einem optimierten Build auf derselben Plattform, der möglicherweise iin einem Register gespeichert ist und niemals im Speicher darauf verweist.
Reis
46
Weil Nichtvorhersehbarkeit eine grundlegende Eigenschaft von undefiniertem Verhalten ist. Sie müssen das verstehen ... Absolut alle Wetten sind aus.
Reis

Antworten:

356

Auf meinem Laptop mit Ubuntu 14.04 wird dieser Code nicht unterbrochen, sondern vollständig ausgeführt. Auf dem Computer meiner Schule mit CentOS 6.6 läuft es auch einwandfrei. Unter Windows 8.1 wird die Schleife nie beendet.

Seltsamer ist, wenn ich die Bedingung der forSchleife auf Folgendes bearbeite : i <= 11Der Code endet nur auf meinem Laptop, auf dem Ubuntu ausgeführt wird. CentOS und Windows werden niemals beendet.

Sie haben gerade das Stampfen von Erinnerungen entdeckt. Hier können Sie mehr darüber lesen: Was ist ein „Memory Stomp“?

Wenn Sie zuweisen int array[10],i;, werden diese Variablen in den Speicher verschoben (insbesondere werden sie auf dem Stapel zugewiesen, der ein mit der Funktion verknüpfter Speicherblock ist). array[]und isind wahrscheinlich nebeneinander im Speicher. Es scheint, dass sich unter Windows 8.1 ibei befindet array[10]. Auf CentOS ibefindet sich bei array[11]. Und unter Ubuntu ist es an keiner Stelle (vielleicht an array[-1]?).

Versuchen Sie, diese Debugging-Anweisungen zu Ihrem Code hinzuzufügen. Sie sollten beachten, dass bei Iteration 10 oder 11 array[i]auf 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; 
} 
FrageC
quelle
6
Hey danke! Das hat wirklich einiges erklärt. In Windows heißt es, dass i, wenn es 10 vom Array versetzt ist, in CentOS und Ubuntu -1 ist. Seltsamer ist, wenn ich Ihren Debugger-Code auskommentiere, CentOS den Code nicht ausführen kann (er hängt), aber mit Ihrem Debugging-Code wird er ausgeführt. C scheint eine sehr Sprache zu sein X_x
JonCav
12
@ JonCav "es hängt" kann passieren, wenn beispielsweise geschrieben wird, um array[10]den Stapelrahmen zu zerstören. Wie kann es einen Unterschied zwischen Code mit oder ohne Debugging-Ausgabe geben? Wenn die Adresse inicht benötigt wird, der Compiler kann optimieren ientfernt. in ein Register, wodurch das Speicherlayout auf dem Stapel geändert wird ...
Hagen von Eitzen
2
Ich glaube nicht, dass es hängt, ich denke, es befindet sich in einer Endlosschleife, weil es den Schleifenzähler aus dem Speicher neu lädt (der gerade auf Null gesetzt wurde array[10]=0. Wenn Sie Ihren Code mit aktivierter Optimierung kompiliert haben, würde dies wahrscheinlich nicht passieren (weil C dies getan hat) Aliasing-Regeln, die einschränken, welche Arten von Speicherzugriffen möglicherweise den anderen Speicher überlappen. Als lokale Variable, deren Adresse Sie nie annehmen, sollte ein Compiler meiner Meinung nach davon ausgehen können, dass nichts sie aliasisiert. Wie auch immer, schreiben Sie das Ende ab eines Arrays ist undefiniertes Verhalten. Versuchen Sie immer, dies zu vermeiden.
Peter Cordes
4
Eine andere Alternative besteht darin, dass ein optimierender Compiler das Array vollständig entfernt, da es keine beobachtbaren Auswirkungen hat (im Originalcode der Frage). Daher könnte der resultierende Code diese konstante Zeichenfolge nur elf Mal ausdrucken, gefolgt vom Drucken der konstanten Größe, wodurch der Überlauf völlig unbemerkt bleibt.
Holger
9
@ JonCav Ich würde allgemein sagen, dass Sie nicht mehr über die Speicherverwaltung wissen müssen und stattdessen einfach nicht wissen müssen, wie Sie undefinierten Code schreiben, insbesondere nicht über das Ende eines Arrays hinaus schreiben ...
T. Kiley
98

Der Fehler liegt zwischen diesen Codeteilen:

int array[10],i;

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

array[i]=0;

Da es arraynur 10 Elemente gibt, ist in der letzten Iteration array[10] = 0;ein Pufferüberlauf. Pufferüberläufe sind UNDEFINIERTES VERHALTEN , was bedeutet, dass sie Ihre Festplatte formatieren oder dazu führen können, dass Dämonen aus Ihrer Nase fliegen.

Es ist ziemlich üblich, dass alle Stapelvariablen nebeneinander angeordnet werden. Wenn ibefindet, in dem array[10]Schreiben auf, dann ist die UB zurückgesetzt wird , ium 0so zur ungekündigten Schleife führt.

Ändern Sie zum Beheben die Schleifenbedingung in i < 10.

o11c
quelle
6
Nitpick: Sie können die Festplatte unter keinem vernünftigen Betriebssystem auf dem Markt formatieren, es sei denn, Sie arbeiten als Root (oder gleichwertig).
Kevin
26
@ Kevin Wenn Sie UB aufrufen, geben Sie jeden Anspruch auf geistige Gesundheit auf.
o11c
7
Es spielt keine Rolle, ob Ihr Code vernünftig ist. Das Betriebssystem lässt Sie das nicht zu.
Kevin
2
@ Kevin Das Beispiel für die Formatierung Ihrer Festplatte entstand lange bevor dies der Fall war. Sogar die Unixe der Zeit (wo C entstand) waren ziemlich glücklich darüber, dass Sie solche Dinge tun konnten - und selbst heute können Sie mit vielen Distributionen gerne alles löschen, rm -rf /auch wenn Sie nicht root sind, nicht Natürlich das gesamte Laufwerk "formatieren", aber trotzdem alle Ihre Daten zerstören. Autsch.
Luaan
5
@ Kevin, aber undefiniertes Verhalten kann eine Betriebssystemanfälligkeit ausnutzen und sich dann selbst erhöhen, um einen neuen Festplattentreiber zu installieren und dann mit dem Scrubben des Laufwerks zu beginnen.
Ratschenfreak
38

In dem letzten Durchlauf der Schleife, in den Sie schreiben array[10], befinden sich jedoch nur 10 Elemente im Array mit den Nummern 0 bis 9. Die C-Sprachspezifikation besagt, dass dies „undefiniertes Verhalten“ ist. In der Praxis bedeutet dies, dass Ihr Programm versucht, in das intunmittelbar darauf liegende arraySpeicherelement zu schreiben . Was dann passiert, hängt davon ab, was tatsächlich dort liegt, und dies hängt nicht nur vom Betriebssystem ab, sondern vor allem vom Compiler, von den Compileroptionen (wie Optimierungseinstellungen), von der Prozessorarchitektur und vom umgebenden Code usw. Es kann sogar von Ausführung zu Ausführung variieren, z. B. aufgrund der Adressraum-Randomisierung (wahrscheinlich nicht in diesem Spielzeugbeispiel, aber es kommt im wirklichen Leben vor). Einige Möglichkeiten umfassen:

  • Der Ort wurde nicht genutzt. Die Schleife endet normal.
  • Der Ort wurde für etwas verwendet, das zufällig den Wert 0 hatte. Die Schleife endet normal.
  • Der Speicherort enthielt die Absenderadresse der Funktion. Die Schleife wird normal beendet, aber dann stürzt das Programm ab, weil es versucht, zur Adresse 0 zu springen.
  • Der Speicherort enthält die Variable i. Die Schleife wird nie beendet, da sie ibei 0 neu gestartet wird.
  • Der Speicherort enthält eine andere Variable. Die Schleife endet normal, aber dann passieren „interessante“ Dinge.
  • Der Speicherort ist eine ungültige Speicheradresse, z. B. weil er arraysich direkt am Ende einer virtuellen Speicherseite befindet und die nächste Seite nicht zugeordnet ist.
  • Dämonen fliegen aus deiner Nase . Glücklicherweise fehlt den meisten Computern die erforderliche Hardware.

Was Sie unter Windows beobachtet haben, war, dass der Compiler beschlossen hat, die Variable iunmittelbar nach dem Array im Speicher abzulegen, und sie array[10] = 0schließlich zugewiesen hat i. Unter Ubuntu und CentOS wurde der Compiler dort nicht platziert i. Fast alle C-Implementierungen gruppieren lokale Variablen im Speicher auf einem Speicherstapel , mit einer Hauptausnahme: Einige lokale Variablen können vollständig in Registern abgelegt werden . Selbst wenn sich die Variable auf dem Stapel befindet, wird die Reihenfolge der Variablen vom Compiler festgelegt und kann nicht nur von der Reihenfolge in der Quelldatei, sondern auch von deren Typen abhängen (um zu vermeiden, dass Speicher für Ausrichtungsbeschränkungen verschwendet wird, die Löcher hinterlassen würden). , auf ihren Namen, auf einem Hash-Wert, der in der internen Datenstruktur eines Compilers verwendet wird, usw.

Wenn Sie herausfinden möchten, was Ihr Compiler beschlossen hat, können Sie ihm sagen, dass er Ihnen den Assembler-Code anzeigen soll. Oh, und lernen Sie, Assembler zu entschlüsseln (es ist einfacher als es zu schreiben). Übergeben Sie mit GCC (und einigen anderen Compilern, insbesondere in der Unix-Welt) die Option -S, Assembler-Code anstelle einer Binärdatei zu erstellen. Hier ist beispielsweise das Assembler-Snippet für die Schleife beim Kompilieren mit GCC auf amd64 mit der Optimierungsoption -O0(keine Optimierung), wobei Kommentare manuell hinzugefügt wurden:

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

Hier befindet sich die Variable i52 Byte unter dem oberen Rand des Stapels, während das Array 48 Byte unter dem oberen Rand des Stapels beginnt. Dieser Compiler hat sich also idirekt vor dem Array platziert. Sie würden überschreiben, iwenn Sie zufällig schreiben würden array[-1]. Wenn Sie ändern array[i]=0zu array[9-i]=0, werden Sie eine Endlosschleife auf dieser speziellen Plattform mit diesen speziellen Compiler - Optionen erhalten.

Jetzt kompilieren wir Ihr Programm mit gcc -O1.

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

Das ist kürzer! Der Compiler hat nicht nur abgelehnt, einen Stapelspeicherort zuzuweisen i- er wird immer nur im Register gespeichert ebx-, sondern er hat sich auch nicht die Mühe gemacht, Speicher zuzuweisen arrayoder Code zum Festlegen seiner Elemente zu generieren, da er festgestellt hat, dass keines der Elemente vorhanden ist werden immer benutzt.

Um dieses Beispiel aussagekräftiger zu gestalten, stellen wir sicher, dass die Array-Zuweisungen ausgeführt werden, indem wir dem Compiler etwas zur Verfügung stellen, das er nicht optimieren kann. Eine einfache Möglichkeit, dies zu tun, besteht darin, das Array aus einer anderen Datei zu verwenden. Aufgrund der separaten Kompilierung weiß der Compiler nicht, was in einer anderen Datei passiert (es sei denn, er optimiert zum Zeitpunkt der Verknüpfung, was gcc -O0oder gcc -O1nicht). Erstellen Sie eine Quelldatei use_array.cmit

void use_array(int *array) {}

und ändern Sie Ihren Quellcode in

#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;
}

Kompilieren mit

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

Diesmal sieht der Assembler-Code folgendermaßen aus:

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

Jetzt befindet sich das Array auf dem Stapel, 44 Bytes von oben. Was ist mit i? Es erscheint nirgendwo! Der Schleifenzähler bleibt jedoch im Register rbx. Es ist nicht genau i, aber die Adresse der array[i]. Der Compiler hat entschieden, dass ies keinen Sinn macht, eine Arithmetik durchzuführen, um zu berechnen, wo bei jedem Durchlauf der Schleife 0 gespeichert werden soll , da der Wert von nie direkt verwendet wurde. Stattdessen ist diese Adresse die Schleifenvariable, und die Arithmetik zum Bestimmen der Grenzen wurde teilweise zur Kompilierungszeit (multiplizieren Sie 11 Iterationen mit 4 Bytes pro Arrayelement, um 44 zu erhalten) und teilweise zur Laufzeit ausgeführt, jedoch ein für alle Mal, bevor die Schleife beginnt ( Führen Sie eine Subtraktion durch, um den Anfangswert zu erhalten.

Selbst in diesem sehr einfachen Beispiel haben wir gesehen, wie das Ändern von Compileroptionen (Aktivierung aktivieren) oder das Ändern von geringfügigen ( array[i]bis array[9-i]) oder sogar das Ändern von scheinbar nicht verwandten Elementen (Hinzufügen des Aufrufs zu use_array) einen signifikanten Unterschied zu dem bewirken kann, was das ausführbare Programm generiert hat vom Compiler tut. Compiler-Optimierungen können viele Dinge bewirken, die für Programme, die undefiniertes Verhalten aufrufen, möglicherweise nicht intuitiv erscheinen . Deshalb bleibt undefiniertes Verhalten völlig undefiniert. Wenn Sie in realen Programmen geringfügig von den Spuren abweichen, kann es selbst für erfahrene Programmierer sehr schwierig sein, die Beziehung zwischen dem, was der Code tut, und dem, was er hätte tun sollen, zu verstehen.

Gilles 'SO - hör auf böse zu sein'
quelle
25

Im Gegensatz zu Java führt C keine Überprüfung der Array-Grenzen durch, dh es gibt keine ArrayIndexOutOfBoundsException. Die Aufgabe, sicherzustellen, dass der Array-Index gültig ist, bleibt dem Programmierer überlassen. Dies absichtlich zu tun führt zu undefiniertem Verhalten, alles könnte passieren.


Für ein Array:

int array[10]

Indizes sind nur im Bereich 0bis gültig 9. Sie versuchen jedoch:

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

Zugriff array[10]hier, ändern Sie die Bedingung ini < 10

Yu Hao
quelle
6
Wenn Sie dies nicht absichtlich tun, führt dies auch zu undefiniertem Verhalten - der Compiler kann es nicht sagen! ;-)
Toby Speight
1
Verwenden Sie einfach ein Makro, um Ihre Fehler als Warnungen auszugeben: #define UNINTENDED_MISTAKE (EXP) printf ("Warnung:" #EXP "Fehler \ n");
lkraider
1
Ich meine, wenn Sie absichtlich einen Fehler machen, können Sie ihn genauso gut als solchen identifizieren und sicher sein, das undefinierte Verhalten zu vermeiden; D
lkraider
19

Sie haben eine Grenzverletzung, und auf den nicht terminierenden Plattformen setzen Sie iam Ende der Schleife versehentlich auf Null, sodass sie erneut beginnt.

array[10]ist ungültig; es enthält 10 Elemente array[0]durcharray[9] und array[10]ist das 11 .. Ihre Schleife sollte wie folgt geschrieben werden, um vorher anzuhalten 10:

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

Wo array[10]Länder auf zwei Ihrer Plattformen implementierungsdefiniert und amüsanterweise definiert sind, landet es auf idiesen Plattformen, auf denen diese Plattformen anscheinend direkt danach liegen array. iwird auf Null gesetzt und die Schleife wird für immer fortgesetzt. Für Ihre anderen Plattformen ikann sich vorher befindenarray oder arraydass sie danach gepolstert sind.

Derek T. Jones
quelle
Ich glaube nicht, dass Valgrind dies fangen kann, da es immer noch ein gültiger Ort ist, aber ASAN kann es.
o11c
13

Sie erklären int array[10]Mittel arrayIndex 0zu 9(insgesamt 10ganzzahligen Elemente es halten kann). Aber die folgende Schleife,

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

wird Schleife 0zu 10bedeutet 11Zeit. Daher, wenn i = 10der Puffer überläuft und undefiniertes Verhalten verursacht .

Versuchen Sie Folgendes:

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

oder,

for (i = 0; i <= 9 ; i++)
rakeb.mazharul
quelle
7

Es ist undefiniert bei array[10]und gibt undefiniertes Verhalten wie zuvor beschrieben. Denken Sie so darüber nach:

Ich habe 10 Artikel in meinem Einkaufswagen. Sie sind:

0: Eine Schachtel Müsli
1: Brot
2: Milch
3: Kuchen
4: Eier
5: Kuchen
6: 2 Liter Soda
7: Salat
8: Burger
9: Eis

cart[10]ist undefiniert und kann in einigen Compilern eine Ausnahme außerhalb der Grenzen geben. Aber viele anscheinend nicht. Der scheinbare 11. Artikel ist ein Artikel, der sich nicht im Warenkorb befindet. Der 11. Punkt zeigt auf einen "Poltergeist-Gegenstand", wie ich ihn nennen werde. Es existierte nie, aber es war da.

Warum einige Compiler ieinen Index von array[10]oder array[11]oder sogar array[-1]angeben, liegt an Ihrer Initialisierungs- / Deklarationsanweisung. Einige Compiler interpretieren dies als:

  • "Ordnen Sie 10 Blöcke ints für array[10]und einen weiteren intBlock zu. Um dies zu vereinfachen, legen Sie sie direkt nebeneinander."
  • Wie zuvor, aber verschieben Sie es ein oder zwei Felder weiter, damit das array[10]nicht darauf hinweist i.
  • Machen Sie dasselbe wie zuvor, aber ordnen Sie es izu array[-1](weil ein Index eines Arrays nicht negativ sein kann oder sollte) oder ordnen Sie es an einer völlig anderen Stelle zu, weil das Betriebssystem damit umgehen kann und es sicherer ist.

Einige Compiler möchten, dass die Dinge schneller gehen, und einige Compiler bevorzugen Sicherheit. Es geht nur um den Kontext. Wenn ich zum Beispiel eine App für das alte BREW-Betriebssystem (das Betriebssystem eines Basistelefons) entwickeln würde, würde es mich nicht um die Sicherheit kümmern. Wenn ich für ein iPhone 6 entwickeln würde, könnte es schnell laufen, egal was passiert, also würde ich einen Schwerpunkt auf Sicherheit legen müssen. (Im Ernst, haben Sie die App Store-Richtlinien von Apple gelesen oder sich über die Entwicklung von Swift und Swift 2.0 informiert?)

DDPWNAGE
quelle
Hinweis: Ich habe die Liste so eingegeben, dass sie "0, 1, 2, 3, 4, 5, 6, 7, 8, 9" lautet, aber die Markup-Sprache von SO hat die Positionen meiner geordneten Liste korrigiert.
DDPWNAGE
6

Da Sie ein Array der Größe 10 erstellt haben, sollte die Schleifenbedingung wie folgt lauten:

int array[10],i;

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

Derzeit versuchen Sie, mithilfe des Speichers auf den nicht zugewiesenen Speicherort zuzugreifen, array[10]und dies verursacht das undefinierte Verhalten . Undefiniertes Verhalten bedeutet, dass sich Ihr Programm unbestimmt verhält, sodass es bei jeder Ausführung unterschiedliche Ausgaben liefern kann.

Steephen
quelle
5

Nun, der C-Compiler sucht traditionell nicht nach Grenzen. Sie können einen Segmentierungsfehler erhalten, wenn Sie sich auf einen Ort beziehen, der nicht zu Ihrem Prozess gehört. Die lokalen Variablen werden jedoch auf dem Stapel zugewiesen. Abhängig von der Art und Weise, wie der Speicher zugewiesen wird, kann der Bereich direkt hinter dem Array ( array[10]) zum Speichersegment des Prozesses gehören. Somit wird keine Segmentierungsfehlerfalle ausgelöst, und das scheinen Sie zu erleben. Wie andere bereits betont haben, ist dies ein undefiniertes Verhalten in C, und Ihr Code kann als fehlerhaft angesehen werden. Da Sie C lernen, sollten Sie sich besser angewöhnen, in Ihrem Code nach Grenzen zu suchen.

unxnut
quelle
4

Abgesehen von der Möglichkeit, dass der Speicher so angelegt ist, dass beim Versuch, a[10]tatsächlich zu schreiben, überschrieben wird i, kann ein optimierender Compiler möglicherweise auch feststellen, dass der Schleifentest nicht mit einem Wert von imehr als zehn erreicht werden kann, ohne dass der Code zuvor auf den Code zugegriffen hat nicht vorhandenes Array-Elementa[10] .

Da ein Versuch, auf dieses Element zuzugreifen, ein undefiniertes Verhalten wäre, hätte der Compiler keine Verpflichtungen hinsichtlich dessen, was das Programm nach diesem Zeitpunkt tun könnte. Da der Compiler in keinem Fall verpflichtet wäre, Code zur Überprüfung des Schleifenindex zu generieren, wenn er größer als zehn sein könnte, wäre er insbesondere nicht verpflichtet, Code zu generieren, um ihn überhaupt zu überprüfen. es könnte stattdessen angenommen werden, dass der <=10Test immer wahr ergibt. Beachten Sie, dass dies auch dann zutrifft, wenn der Code ihn a[10]eher liest als schreibt.

Superkatze
quelle
3

Wenn Sie vorbei iterieren i==9, weisen Sie den 'Array-Elementen', die sich tatsächlich hinter dem Array befinden, Null zu , sodass Sie einige andere Daten überschreiben. Höchstwahrscheinlich überschreiben Sie die iVariable, die sich danach befindet a[]. Auf diese Weise setzen Sie die Variable einfach auf Null zurücki und starten so die Schleife neu.

Sie könnten das selbst entdecken, wenn Sie iin der Schleife drucken :

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

statt nur

      printf("test \n");

Natürlich hängt dieses Ergebnis stark von der Speicherzuordnung für Ihre Variablen ab, die wiederum von einem Compiler und seinen Einstellungen abhängt. Daher handelt es sich im Allgemeinen um undefiniertes Verhalten. Aus diesem Grund können die Ergebnisse auf verschiedenen Computern oder verschiedenen Betriebssystemen oder auf verschiedenen Compilern unterschiedlich sein.

CiaPan
quelle
0

Der Fehler liegt im Teilarray [10]. w / c ist auch die Adresse von i (int array [10], i;). Wenn Array [10] auf 0 gesetzt ist, würde i 0 sein. w / c setzt die gesamte Schleife zurück und verursacht die Endlosschleife. Es wird eine Endlosschleife geben, wenn Array [10] zwischen 0 und 10 liegt. Die richtige Schleife sollte für (i = 0; i <10; i ++) {...} int Array [10], i; für (i = 0; i <= 10; i ++) Array [i] = 0;

Jonelle H. Castaneda
quelle
0

Ich werde etwas vorschlagen, das ich oben nicht finde:

Versuchen Sie, Array [i] = 20 zuzuweisen.

Ich denke, dies sollte den Code überall beenden. (Vorausgesetzt, Sie behalten i <= 10 oder ll)

Wenn dies ausgeführt wird, können Sie fest entscheiden, dass die hier angegebenen Antworten bereits korrekt sind [die Antwort bezieht sich beispielsweise auf das Stampfen des Speichers].

Feuer regnen
quelle
-9

Hier sind zwei Dinge falsch. Das int i ist tatsächlich ein Array-Element, Array [10], wie auf dem Stapel zu sehen. Da Sie der Indizierung erlaubt haben, Array [10] = 0 zu machen, wird der Schleifenindex i niemals 10 überschreiten. Machen Sie es for(i=0; i<10; i+=1).

i ++ ist, wie K & R es nennen würde, "schlechter Stil". Es erhöht i um die Größe von i, nicht um 1. i ++ steht für Zeigermathematik und i + = 1 steht für Algebra. Dies hängt zwar vom Compiler ab, ist jedoch keine gute Konvention für die Portabilität.

SkipBerne
quelle
5
-1 Völlig falsch. Variable iist NOTan Array-Element a[10], es gibt keine Verpflichtung oder sogar einen Vorschlag für einen Compiler, es unmittelbar danach auf den Stapel zu legen a[]- es kann auch vor dem Array platziert oder mit etwas zusätzlichem Speicherplatz getrennt werden. Es könnte sogar außerhalb des Hauptspeichers zugewiesen werden, beispielsweise in einem CPU-Register. Es ist auch falsch, dass ++für Zeiger und nicht für ganze Zahlen. Völlig falsch ist 'i ++ erhöht i um die Größe von i' - lesen Sie die Operatorbeschreibung in der Sprachdefinition!
CiaPan
Deshalb funktioniert es auf einigen Plattformen und nicht auf anderen. Dies ist die einzig logische Erklärung dafür, warum es unter Windows für immer Schleifen gibt. In Bezug auf I ++ ist Zeigermathematik keine Ganzzahl. Lesen Sie die heiligen Schriften ... die 'C-Programmiersprache'. von Kernigan und Ritche, wenn Sie wollen, habe ich ein handsigniertes Exemplar und programmiere seit 1981 in c.
SkipBerne
1
Lesen Sie den Quellcode von OP und suchen Sie die Deklaration der Variablen i- sie ist vom intTyp. Es ist eine ganze Zahl , kein Zeiger; eine Ganzzahl, die als Index für die array,.
CiaPan
1
Ich habe es getan und deshalb habe ich es so kommentiert wie ich. Vielleicht sollten Sie sich darüber im Klaren sein, dass es in einigen Kompilierungen bei einigen Kompilierungen tatsächlich auf den Array-Index verweist, wenn der Compiler keine Stapelprüfungen enthält, und in diesem Fall als Stapelreferenz, wenn I = 10, tatsächlich innerhalb der Grenzen des Stapelbereichs liegt. Compiler können nicht dumm beheben. Kompilierungen könnten eine Korrektur vornehmen, wie es scheint, aber eine reine Interpretation der Programmiersprache c würde diese Konvention nicht unterstützen und würde, wie das OP sagte, zu nicht portablen Ergebnissen führen.
SkipBerne
@SkipBerne: Erwägen Sie, Ihre Antwort zu löschen, bevor Sie mit mehr negativen Punkten "belohnt" werden.
Peter VARGA