Dlaczego powoduje to wyjście pętli na niektórych platformach, a nie na innych?

240

Niedawno zacząłem uczyć się C i uczęszczam na zajęcia z C jako przedmiot. Obecnie bawię się pętlami i napotykam dziwne zachowania, których nie umiem wyjaśnić.

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

Na moim laptopie z systemem Ubuntu 14.04 ten kod się nie psuje. Biegnie do końca. Na komputerze mojej szkoły z systemem CentOS 6.6 również działa dobrze. W systemie Windows 8.1 pętla nigdy się nie kończy.

Jeszcze bardziej dziwne jest to, że kiedy edytuję stan forpętli do: i <= 11kod kończy się tylko na moim laptopie z systemem Ubuntu. Nigdy nie kończy się w CentOS i Windows.

Czy ktoś może wyjaśnić, co dzieje się w pamięci i dlaczego różne systemy operacyjne obsługujące ten sam kod dają różne wyniki?

EDYCJA: Wiem, że pętla for wychodzi poza granice. Robię to celowo. Po prostu nie mogę zrozumieć, jak zachowanie może się różnić w różnych systemach operacyjnych i komputerach.

JonCav
źródło
147
Ponieważ przepełnisz tablicę, występuje niezdefiniowane zachowanie. Niezdefiniowane zachowanie oznacza, że ​​wszystko może się zdarzyć, w tym wydaje się, że działa. Dlatego „kod nigdy nie powinien kończyć się” nie jest poprawnym oczekiwaniem.
kaylum
37
Dokładnie, witamy w C. Twoja tablica ma 10 elementów - ponumerowanych od 0 do 9.
Yetti99
14
@JonCav Złamałeś kod. Otrzymujesz niezdefiniowane zachowanie, które jest uszkodzonym kodem.
kaylum
50
Chodzi o to, że niezdefiniowane zachowanie jest dokładnie tym. Nie można go wiarygodnie przetestować i udowodnić, że coś się wydarzy. Prawdopodobnie dzieje się na twoim komputerze z systemem Windows, że zmienna ijest przechowywana zaraz po zakończeniu arrayi zastępujesz ją array[10]=0;. Może tak nie być w przypadku zoptymalizowanej wersji na tej samej platformie, która może przechowywać iw rejestrze i nigdy nie odwoływać się do niego w pamięci.
paddy
46
Ponieważ nieprzewidywalność jest podstawową właściwością Nieokreślonego Zachowania. Musisz to zrozumieć ... Absolutnie wszystkie zakłady są wyłączone.
paddy

Odpowiedzi:

356

Na moim laptopie z systemem Ubuntu 14.04 ten kod nie psuje się, działa do końca. Na komputerze mojej szkoły z systemem CentOS 6.6 również działa dobrze. W systemie Windows 8.1 pętla nigdy się nie kończy.

Co dziwniejsze, kiedy edytuję warunek forpętli do: i <= 11kod kończy się tylko na moim laptopie z systemem Ubuntu. CentOS i Windows nigdy się nie kończą.

Właśnie odkryłeś tupanie w pamięci. Możesz przeczytać więcej na ten temat tutaj: Co to jest „stomp pamięci”?

Po przydzieleniu int array[10],i;zmienne te przechodzą do pamięci (w szczególności są przydzielane na stosie, który jest blokiem pamięci związanym z funkcją). array[]i iprawdopodobnie przylegają do siebie w pamięci. Wygląda na to, że w systemie Windows 8.1 iznajduje się na array[10]. Na CentOS iznajduje się pod adresem array[11]. A w Ubuntu nie ma go w żadnym miejscu (może jest w array[-1]?).

Spróbuj dodać te instrukcje debugowania do swojego kodu. Powinieneś zauważyć, że przy iteracji 10 lub 11, array[i]punkty na 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; 
} 
Pytanie C.
źródło
6
Hej dzięki! To naprawdę trochę wyjaśniło. W systemie Windows stwierdza, że ​​jeśli przesunięto 10 względem tablicy, podczas gdy zarówno w CentOS, jak i Ubuntu, wynosi -1. Dziwniejsze jest to, że jeśli skomentuję twój kod debuggera, CentOS nie może uruchomić kodu (zawiesza się), ale działa z twoim kodem debugującym. C wydaje się jak dotąd bardzo językiem X_x
JonCav
12
@JonCav „zawiesza się” może się zdarzyć, jeśli na przykład zapisanie array[10]zniszczy ramkę stosu. Jak może istnieć różnica między kodem z wyjściem debugującym a bez niego? Jeśli adres inigdy nie jest potrzebny, kompilator może się zoptymalizować i. do rejestru, zmieniając w ten sposób układ pamięci na stosie ...
Hagen von Eitzen
2
Nie sądzę, że się zawiesza, myślę, że jest w nieskończonej pętli, ponieważ przeładowuje licznik pętli z pamięci (który został właśnie wyzerowany array[10]=0. Jeśli skompilowałeś swój kod z optymalizacją na, prawdopodobnie tak się nie stanie (ponieważ C ma zasady aliasingu, które ograniczają rodzaje dostępu do pamięci, mogą potencjalnie nakładać się na inną pamięć. Jako zmienna lokalna, której nigdy nie bierzesz pod uwagę, myślę, że kompilator powinien być w stanie założyć, że nic nie aliasy. W każdym razie, spisując koniec tablicy jest niezdefiniowanym zachowaniem. Zawsze staraj się w zależności od tego unikać
Peter Cordes
4
Inną alternatywą jest to, że optymalizujący kompilator całkowicie usuwa tablicę, ponieważ nie ma zauważalnego efektu (w oryginalnym kodzie pytania). Dlatego wynikowy kod mógłby po prostu wydrukować ten stały ciąg jedenastokrotnie, a następnie wydrukować stały rozmiar, a zatem przepełnienie byłoby całkowicie niezauważalne.
Holger
9
@JonCav Ogólnie powiedziałbym, że nie musisz wiedzieć więcej o zarządzaniu pamięcią, a zamiast tego po prostu wiedzieć, że nie pisać niezdefiniowanego kodu, a konkretnie nie pisać poza końcem tablicy ...
T. Kiley
98

Błąd leży między tymi fragmentami kodu:

int array[10],i;

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

array[i]=0;

Ponieważ arrayma tylko 10 elementów, w ostatniej iteracji array[10] = 0;występuje przepełnienie bufora. Przepełnienia buforów są NIEZDEFINIOWANE ZACHOWANIA , co oznacza, że ​​mogą sformatować dysk twardy lub spowodować, że demony wylecą z nosa.

Dość często zdarza się, że wszystkie zmienne stosu są układane obok siebie. Jeśli iznajduje się tam, gdzie array[10]zapisuje, UB zresetuje się ido 0, co prowadzi do pętli niezakończonej.

Aby naprawić, zmień warunek pętli na i < 10.

o11c
źródło
6
Nitpick: Nie możesz sformatować dysku twardego w żadnym rozsądnym systemie operacyjnym na rynku, chyba że działasz jako root (lub odpowiednik).
Kevin
26
@Kevin, gdy odwołujesz się do UB, rezygnujesz z wszelkich roszczeń dotyczących zdrowia psychicznego.
o11c
7
Nie ma znaczenia, czy Twój kod jest rozsądny. System operacyjny nie pozwoli ci tego zrobić.
Kevin
2
@Kevin Przykład formatowania dysku twardego powstał na długo przedtem. Nawet ówczesne uniksy (z których pochodzi C) były całkiem zadowolone z tego, że możesz robić takie rzeczy - i nawet dziś, wiele dystrybucji pozwoli ci na rozpoczęcie usuwania wszystkiego, rm -rf /nawet jeśli nie jesteś rootem, a nie „formatuje” cały dysk oczywiście, ale wciąż niszczy wszystkie twoje dane. Auć.
Luaan,
5
@Kevin, ale niezdefiniowane zachowanie może wykorzystać lukę w systemie operacyjnym, a następnie podnieść się, aby zainstalować nowy sterownik dysku twardego, a następnie rozpocząć szorowanie dysku.
maniak zapadkowy
38

W tym, co powinno być ostatnim uruchomieniem pętli, piszesz array[10], ale w tablicy jest tylko 10 elementów, ponumerowanych od 0 do 9. Specyfikacja języka C mówi, że jest to „niezdefiniowane zachowanie”. W praktyce oznacza to, że twój program będzie próbował zapisać intw pamięci o odpowiednim rozmiarze, która leży bezpośrednio po niej arrayw pamięci. To, co się dzieje, zależy od tego, co tam właściwie leży, a to zależy nie tylko od systemu operacyjnego, ale bardziej od kompilatora, opcji kompilatora (takich jak ustawienia optymalizacji), architektury procesora, otaczającego kodu itp. Może nawet różnić się w zależności od wykonania, np. z powodu randomizacji przestrzeni adresowej (prawdopodobnie nie w tym przykładzie zabawki, ale dzieje się tak w prawdziwym życiu). Niektóre możliwości obejmują:

  • Lokalizacja nie była używana. Pętla kończy się normalnie.
  • Lokalizacja została użyta do czegoś, co miało wartość 0. Pętla kończy się normalnie.
  • Lokalizacja zawierała adres zwrotny funkcji. Pętla kończy się normalnie, ale następnie program ulega awarii, ponieważ próbuje przejść do adresu 0.
  • Lokalizacja zawiera zmienną i. Pętla nigdy się nie kończy, ponieważ iuruchamia się ponownie od 0.
  • Lokalizacja zawiera inną zmienną. Pętla kończy się normalnie, ale zdarzają się „interesujące” rzeczy.
  • Lokalizacja jest nieprawidłowym adresem pamięci, np. Ponieważ arrayznajduje się na końcu strony pamięci wirtualnej, a następna strona nie jest mapowana.
  • Demony wylatują z twojego nosa . Na szczęście większość komputerów nie ma wymaganego sprzętu.

W systemie Windows zaobserwowano, że kompilator postanowił umieścić zmienną ibezpośrednio po tablicy w pamięci, więc array[10] = 0ostatecznie przypisał ją i. W Ubuntu i CentOS kompilator nie umieścił itam. Prawie wszystkie implementacje języka C grupują zmienne lokalne w pamięci na stosie pamięci , z jednym głównym wyjątkiem: niektóre zmienne lokalne można całkowicie umieścić w rejestrach . Nawet jeśli zmienna znajduje się na stosie, kolejność zmiennych jest określana przez kompilator i może zależeć nie tylko od kolejności w pliku źródłowym, ale także od ich typów (aby uniknąć marnowania pamięci na ograniczenia wyrównania, które pozostawią dziury) , na ich nazwy, na niektóre wartości skrótu używane w wewnętrznej strukturze danych kompilatora itp.

Jeśli chcesz dowiedzieć się, co zrobił twój kompilator, możesz mu powiedzieć, żeby pokazał ci kod asemblera. Aha i naucz się rozszyfrowywać asembler (to łatwiejsze niż pisanie). W GCC (i niektórych innych kompilatorach, szczególnie w świecie Unixowym), podaj opcję -Stworzenia kodu asemblera zamiast pliku binarnego. Na przykład, oto fragment asemblera dla kompilacji pętli z GCC na amd64 z opcją optymalizacji -O0(bez optymalizacji), z komentarzami dodanymi ręcznie:

.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

Tutaj zmienna iznajduje się 52 bajty poniżej górnej części stosu, podczas gdy tablica zaczyna 48 bajtów poniżej górnej części stosu. Tak się składa, że ​​ten kompilator umieścił ituż przed tablicą; nadpisujesz, ijeśli zdarzy ci się pisać array[-1]. Jeśli zmienisz array[i]=0na array[9-i]=0, dostaniesz nieskończoną pętlę na tej konkretnej platformie z tymi konkretnymi opcjami kompilatora.

Teraz skompilujmy Twój program gcc -O1.

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

To krócej! Kompilator nie tylko odmówił przydzielenia lokalizacji stosu i- jest on zawsze przechowywany w rejestrze ebx- ale nie zadał sobie trudu, aby przydzielić pamięć arraylub wygenerować kod do ustawienia swoich elementów, ponieważ zauważył, że żaden z elementów są kiedykolwiek używane.

Aby ten przykład był bardziej wymowny, upewnijmy się, że przypisania tablic są wykonywane przez dostarczenie kompilatorowi czegoś, czego nie jest w stanie zoptymalizować. Prostym sposobem na to jest użycie tablicy z innego pliku - z powodu osobnej kompilacji kompilator nie wie, co dzieje się w innym pliku (chyba że zoptymalizuje się w czasie łącza, który nie, gcc -O0lub gcc -O1nie). Utwórz plik źródłowy use_array.czawierający

void use_array(int *array) {}

i zmień kod źródłowy na

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

Połącz z

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

Tym razem kod asemblera wygląda następująco:

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

Teraz tablica jest na stosie, 44 bajty od góry. Co i? Nigdzie się nie pojawia! Ale licznik pętli jest przechowywany w rejestrze rbx. To nie jest dokładnie i, ale adres array[i]. Kompilator zdecydował, że skoro wartość inigdy nie była używana bezpośrednio, nie było sensu wykonywać arytmetyki, aby obliczyć, gdzie przechowywać 0 podczas każdego przebiegu pętli. Zamiast tego ten adres jest zmienną pętli, a arytmetyka określająca granice została wykonana częściowo w czasie kompilacji (pomnóż 11 iteracji przez 4 bajty na element tablicy, aby uzyskać 44), a częściowo w czasie wykonywania, ale raz na zawsze, zanim pętla się rozpocznie ( wykonaj odejmowanie, aby uzyskać wartość początkową).

Nawet na tym bardzo prostym przykładzie widzieliśmy, jak zmiana opcji kompilatora (włączenie optymalizacji) lub zmiana czegoś drobnego ( array[i]na array[9-i]) lub nawet zmiana czegoś pozornie niezwiązanego (dodanie wywołania use_array) może mieć znaczący wpływ na to, co program wykonywalny wygenerował przez kompilator. Optymalizacje kompilatora mogą zrobić wiele rzeczy, które mogą wydawać się nieintuicyjne w programach wywołujących niezdefiniowane zachowanie . Dlatego niezdefiniowane zachowanie jest całkowicie niezdefiniowane. Nawet jeśli nieco odbiegasz od ścieżek, w rzeczywistych programach może być bardzo trudno zrozumieć związek między tym, co robi kod, a tym, co powinien był zrobić, nawet dla doświadczonych programistów.

Gilles „SO- przestań być zły”
źródło
25

W przeciwieństwie do Javy, C nie wykonuje sprawdzania granic tablic, tzn. Nie ArrayIndexOutOfBoundsException, zadanie upewnienia się, że indeks tablicy jest prawidłowy, pozostaje w gestii programisty. Robienie tego celowo prowadzi do nieokreślonego zachowania, wszystko może się zdarzyć.


W przypadku tablicy:

int array[10]

indeksy są poprawne tylko w zakresie 0od 9. Próbujesz jednak:

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

dostęp array[10]tutaj, zmień warunek nai < 10

Yu Hao
źródło
6
Wykonanie tego niecelowo prowadzi również do nieokreślonego zachowania - kompilator nie może powiedzieć! ;-)
Toby Speight
1
Po prostu użyj makra, aby przesyłać swoje błędy jako ostrzeżenia: # zdefiniuj NIEINTENDED_MISTAKE (EXP) printf („Ostrzeżenie: błąd„ #EXP ”\ n”);
lkraider
1
Mam na myśli, że jeśli popełnisz błąd celowo, równie dobrze możesz go zidentyfikować i zabezpieczyć przed niezdefiniowanym zachowaniem; D
lkraider
19

Masz naruszenie granicy, a na platformach nie kończących się, uważam, że przypadkowo ustawiasz izero na końcu pętli, aby zaczęło się od nowa.

array[10]jest nieważny; zawiera 10 elementów, array[0]przez array[9]i array[10]jest 11. Pętla powinno być napisane, aby zatrzymać przed 10 , jak następuje:

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

Tam, gdzie array[10]ziemie są zdefiniowane implementacyjnie i zabawnie, na dwóch twoich platformach, ląduje na nich i, na których te platformy najwyraźniej bezpośrednio po sobie układają array. ijest ustawiony na zero, a pętla trwa wiecznie. W przypadku innych platform imoże znajdować się wcześniej arraylub arraymoże po nim mieć trochę wypełnienia.

Derek T. Jones
źródło
Nie sądzę, by valgrind mógł to złapać, ponieważ nadal jest to poprawna lokalizacja, ale ASAN może.
o11c
13

Deklarujesz, int array[10]że średnia arrayma indeks 0do 9( 10liczba elementów całkowitych, które może pomieścić). Ale następująca pętla

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

zapętli się 0na 10oznacza 11czas. Stąd kiedy i = 10przepełni bufor i spowoduje Niezdefiniowane zachowanie .

Spróbuj tego:

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

lub,

for (i = 0; i <= 9 ; i++)
rakeb.mazharul
źródło
7

Jest niezdefiniowany w array[10]i daje niezdefiniowane zachowanie, jak opisano wcześniej. Pomyśl o tym w ten sposób:

Mam 10 produktów w koszyku spożywczym. Oni są:

0: Pudełko płatków
1: Chleb
2: Mleko
3: Ciasto
4: Jajka
5: Ciasto
6: 2 litry sody
7: Sałatka
8: Burgery
9: Lody

cart[10]jest niezdefiniowany i może dawać wyjątek poza zakresem w niektórych kompilatorach. Ale wiele najwyraźniej nie. Pozornie jedenasty przedmiot to przedmiot, którego faktycznie nie ma w koszyku. Jedenasty przedmiot wskazuje, jak to nazywam, „przedmiot poltergeist”. Nigdy nie istniał, ale tam był.

Dlaczego niektóre kompilatory dać iindeks array[10]lub array[11]nawet array[-1]z powodu wyciągu inicjalizacji / deklaracji. Niektóre kompilatory interpretują to jako:

  • „Przydziel 10 bloków ints array[10]i kolejny intblok. Aby to ułatwić, umieść je obok siebie”.
  • Tak jak poprzednio, ale przesuń go o jedną lub dwie spacje, aby array[10]to nie wskazywało i.
  • Zrób to samo co poprzednio, ale alokuj io array[-1](ponieważ indeks tablicy nie może lub nie powinien być ujemny) lub alokuj go w zupełnie innym miejscu, ponieważ system operacyjny może to obsłużyć, i jest bezpieczniejszy.

Niektóre kompilatory chcą, aby wszystko szło szybciej, a niektóre kompilatory wolą bezpieczeństwo. Chodzi o kontekst. Gdybym na przykład rozwijał aplikację dla starożytnego systemu operacyjnego BREW (system operacyjny podstawowego telefonu), nie miałoby to znaczenia dla bezpieczeństwa. Gdybym opracowywał dla iPhone'a 6, to mógłby działać szybko, bez względu na wszystko, więc musiałbym położyć nacisk na bezpieczeństwo. (Poważnie, czy przeczytałeś Wytyczne Apple App Store lub poczytałeś o rozwoju Swift i Swift 2.0?)

DDPWNAGE
źródło
Uwaga: wpisałem listę, więc brzmi „0, 1, 2, 3, 4, 5, 6, 7, 8, 9”, ale język znaczników SO naprawił pozycje mojej uporządkowanej listy.
DDPWNAGE,
6

Ponieważ utworzyłeś tablicę o rozmiarze 10, warunek pętli powinien wyglądać następująco:

int array[10],i;

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

Obecnie próbujesz uzyskać dostęp do nieprzypisanej lokalizacji z pamięci, array[10]co powoduje niezdefiniowane zachowanie . Niezdefiniowane zachowanie oznacza, że ​​Twój program będzie zachowywał się w nieokreślony sposób, dzięki czemu może dawać różne wyniki w każdym wykonaniu.

Steephen
źródło
5

Kompilator C tradycyjnie nie sprawdza granic. Możesz otrzymać błąd segmentacji w przypadku odniesienia do lokalizacji, która nie „należy” do Twojego procesu. Jednak zmienne lokalne są alokowane na stosie i zależnie od sposobu alokacji pamięci obszar tuż za tablicą ( array[10]) może należeć do segmentu pamięci procesu. W ten sposób nie powstaje pułapka błędu segmentacji i wydaje się, że tego doświadczasz. Jak zauważyli inni, jest to niezdefiniowane zachowanie w C i twój kod może być uważany za niepoprawny. Ponieważ uczysz się języka C, lepiej jest przyzwyczaić się do sprawdzania granic w kodzie.

unxnut
źródło
4

Poza możliwością, że pamięć może być tak ułożona, że ​​próba zapisu w celu a[10]nadpisania i, może być również możliwe, że kompilator optymalizacyjny może ustalić, że test pętli nie może zostać osiągnięty przy wartości iwiększej niż dziesięć bez uprzedniego uzyskania dostępu do kodu nieistniejący element tablicy a[10].

Ponieważ próba dostępu do tego elementu byłaby nieokreślonym zachowaniem, kompilator nie miałby żadnych zobowiązań w odniesieniu do tego, co program mógłby zrobić po tym punkcie. Mówiąc dokładniej, ponieważ kompilator nie miałby obowiązku generowania kodu w celu sprawdzenia indeksu pętli w żadnym przypadku, w którym może on być większy niż dziesięć, nie miałby obowiązku generowania kodu w celu sprawdzenia go w ogóle; zamiast tego można założyć, że <=10test zawsze da wynik prawdziwy. Zauważ, że byłoby to prawdą, nawet gdyby kod czytał, a[10]a nie pisał.

supercat
źródło
3

Podczas iteracji w przeszłości i==9przypisujesz zero do „elementów tablicy”, które faktycznie znajdują się za tablicą , więc zastępujesz niektóre inne dane. Najprawdopodobniej nadpisujesz izmienną, która znajduje się po a[]. W ten sposób po prostu resetujesz izmienną do zera, a tym samym restartujesz pętlę.

Możesz to odkryć sam, jeśli drukujesz iw pętli:

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

zamiast po prostu

      printf("test \n");

Oczywiście wynik ten silnie zależy od przydziału pamięci dla zmiennych, co z kolei zależy od kompilatora i jego ustawień, więc ogólnie jest to zachowanie niezdefiniowane - dlatego wyniki na różnych komputerach lub w różnych systemach operacyjnych lub na różnych kompilatorach mogą się różnić.

CiaPan
źródło
0

błąd jest w porcji tablica [10] w / c to także adres i (int tablica [10], i;). gdy tablica [10] jest ustawiona na 0, to i byłoby 0 w / c resetuje całą pętlę i powoduje nieskończoną pętlę. będzie nieskończona pętla, jeśli tablica [10] zawiera się w przedziale od 0-10. poprawna pętla powinna być dla (i = 0; i <10; i ++) {...} int array [10], i; dla (i = 0; i <= 10; i ++) tablica [i] = 0;

Jonelle H. Castaneda
źródło
0

Zasugeruję coś, czego nie znajdę powyżej:

Spróbuj przypisać tablicę [i] = 20;

Myślę, że to powinno zakończyć kod wszędzie. (Pod warunkiem, że trzymasz i <= 10 lub ll)

Jeśli to się uruchomi, możesz zdecydowanie zdecydować, że podane tutaj odpowiedzi są już poprawne [odpowiedź związana z tupiącą pamięcią np.]

Pada ogień
źródło
-9

Są tu dwie rzeczy źle. Int i to tak naprawdę element tablicy, tablica [10], jak widać na stosie. Ponieważ zezwoliłeś, aby indeksowanie faktycznie tworzyło tablicę [10] = 0, indeks pętli i nigdy nigdy nie przekroczy 10. Zrób to for(i=0; i<10; i+=1).

i ++ jest, jak nazwałby to K&R , „złym stylem”. Inkrementuje i o wielkość i, a nie 1. i ++ jest dla matematyki wskaźnika, a i + = 1 jest dla algebry. Chociaż zależy to od kompilatora, nie jest to dobra konwencja dotycząca przenośności.

SkipBerne
źródło
5
-1 Zupełnie źle. Zmienna ijest elementem tablicy NOTan a[10], nie ma obowiązku ani nawet sugestii, aby kompilator umieścił ją na stosie natychmiast po a[] niej - równie dobrze może być umieszczony przed tablicą lub oddzielony dodatkową przestrzenią. Można go nawet przydzielić poza pamięć główną, na przykład w rejestrze procesora. To nieprawda, że ++dotyczy wskaźników, a nie liczb całkowitych. Całkowicie błędne jest to, że „i ++ zwiększa i o wielkość i” - przeczytaj opis operatora w definicji języka!
CiaPan
dlatego działa na niektórych platformach, a nie na innych. jest to jedyne logiczne wyjaśnienie, dlaczego zawsze zapętla się w systemie Windows. w odniesieniu do I ++ matematyka wskaźnikowa nie jest liczbą całkowitą. przeczytaj Pismo Święte ... „język programowania C”. autor: Kernigan i Ritche, jeśli chcesz, mam kopię z autografem i programuję c od 1981 roku.
SkipBerne
1
Przeczytaj kod źródłowy przez OP i znajdź deklarację zmiennej i- jest inttypu. Jest to liczba całkowita , a nie wskaźnik; liczbą całkowitą, używane jako indeks do array,.
CiaPan,
1
Tak zrobiłem i dlatego tak skomentowałem. być może powinieneś zdawać sobie sprawę, że jeśli kompilator nie obejmuje sprawdzania stosu, w tym przypadku nie miałoby to znaczenia jako odwołanie do stosu, gdy I = 10 faktycznie odnosi się, w niektórych kompilacjach, do indeksu tablicy i który mieści się w granicach regionu stosu. kompilatory nie mogę naprawić głupie. kompilacje mogą dokonać naprawy, tak jak się wydaje, ale czysta interpretacja języka programowania c nie obsługuje tej konwencji i, jak powiedział PO, skutkuje nieprzystosowanymi wynikami.
SkipBerne
@SkipBerne: Zastanów się nad usunięciem odpowiedzi, zanim zostaniesz „nagrodzony” dodatkowymi punktami ujemnymi.
Peter VARGA