Natknąłem się na dziwne doświadczenie w programowaniu C. Rozważ ten kod:
int main(){
int array1[6] = {0, 1, 2, 3, 4, 5};
int array2[6] = {6, 7, 8, 9, 10, 11};
printf("%d\n", array1[-1]);
return 0;
}
Podczas kompilacji i uruchamiania nie otrzymuję żadnych błędów ani ostrzeżeń. Jak powiedział mój wykładowca, indeks tablicy -1
uzyskuje dostęp do innej zmiennej. Nadal jestem zdezorientowany, dlaczego do cholery język programowania ma taką możliwość? Chodzi mi o to, dlaczego zezwalać na ujemne wskaźniki tablicowe?
programming-languages
arrays
c
Mohammed Fawzan
źródło
źródło
-1
podtablicy jest całkowicie poprawnym sposobem na odniesienie się do elementu przed tą tablicą w większej tablicy. Po drugie, jeśli indeks jest nieprawidłowy, program jest nieprawidłowy, ale w większości implementacji dostaniesz ciche, złe zachowanie, a nie błąd poza zakresem.Odpowiedzi:
Operacja indeksowania tablic
a[i]
zyskuje na znaczeniu z następujących funkcji języka C.Składnia
a[i]
jest równoważna z*(a + i)
. Dlatego warto powiedzieć,5[a]
aby dostać się do piątego elementua
.Pointer-arytmetyka mówi, że biorąc pod uwagę wskaźnik
p
i liczbę całkowitąi
,p + i
wskaźnikp
przesuwa się oi * sizeof(*p)
bajtyNazwa tablicy
a
bardzo szybko zmienia się w wskaźnik do 0-tego elementua
W efekcie indeksowanie tablic jest szczególnym przypadkiem indeksowania wskaźników. Ponieważ wskaźnik może wskazywać dowolne miejsce w tablicy, każde dowolne wyrażenie, które wygląda, nie
p[-1]
jest błędne podczas badania, a zatem kompilatory nie (nie mogą) traktować wszystkich takich wyrażeń jako błędów.Twój przykład,
a[-1]
gdziea
tak naprawdę jest nazwa tablicy, jest w rzeczywistości nieprawidłowy. IIRC, nie jest zdefiniowane, jeśli istnieje znacząca wartość wskaźnika jako wynik wyrażenia, oa - 1
któryma
wiadomo, że jest wskaźnikiem do 0-tego elementu tablicy. Sprytny kompilator może to wykryć i oznaczyć jako błąd. Inne kompilatory mogą być nadal zgodne, pozwalając ci strzelać sobie w stopę, dając ci wskaźnik do losowego stosu.Odpowiedź informatyki brzmi:
W C
[]
operator jest zdefiniowany na wskaźnikach, a nie tablicach. W szczególności jest to zdefiniowane w kategoriach arytmetyki wskaźnika i dereferencji wskaźnika.W C wskaźnik jest abstrakcyjnie krotką
(start, length, offset)
z warunkiem, że0 <= offset <= length
. Arytmetyka wskaźnika jest zasadniczo podnoszona arytmetyką na przesunięciu, z zastrzeżeniem, że jeśli wynik operacji narusza warunek wskaźnika, jest to wartość niezdefiniowana. Usunięcie odwołania ze wskaźnika dodaje dodatkowe ograniczenie, któreoffset < length
.C ma pojęcie,
undefined behaviour
które pozwala kompilatorowi konkretnie reprezentować tę krotkę jako pojedynczą liczbę i nie musi wykrywać żadnych naruszeń warunku wskaźnika. Każdy program, który spełnia semantykę abstrakcyjną, będzie bezpieczny z semantyką konkretną (stratną). Wszystko, co narusza semantykę abstrakcyjną, może zostać bez komentarza zaakceptowane przez kompilator i może zrobić z nim wszystko, co chce.źródło
Tablice są po prostu ułożone jako ciągłe fragmenty pamięci. Dostęp do tablicy, taki jak [i], jest konwertowany na dostęp do adresu lokalizacji pamięciOf (a) + i. Ten kod
a[-1]
jest doskonale zrozumiały, po prostu odnosi się do adresu przed początkiem tablicy.To może wydawać się szalone, ale jest wiele powodów, dla których jest to dozwolone:
a[-1]
. Na przykład, jeśli wiem, żea
tak naprawdę nie jest to początek tablicy, ale wskaźnik na środku tablicy, toa[-1]
po prostu pobiera element tablicy, który znajduje się po lewej stronie wskaźnika.źródło
a[-1]
ma sens w niektórych przypadkacha
, w tym konkretnym przypadku jest to po prostu nielegalne (ale nie złapane przez kompilator)Jak wyjaśniają inne odpowiedzi, jest to niezdefiniowane zachowanie w C. Weź pod uwagę, że C zostało zdefiniowane (i jest najczęściej używane) jako „asembler wysokiego poziomu”. Użytkownicy C cenią go za jego bezkompromisową szybkość, a sprawdzanie rzeczy w czasie wykonywania jest (głównie) wykluczone ze względu na samą wydajność. Niektóre konstrukcje C, które wyglądają na nonsensowne dla ludzi pochodzących z innych języków, mają w C idealny sens, tak jak to
a[-1]
. Tak, to nie zawsze ma sens (źródło
Można użyć takiej funkcji do napisania metod alokacji pamięci, które mają bezpośredni dostęp do pamięci. Jednym z takich zastosowań jest sprawdzenie poprzedniego bloku pamięci za pomocą ujemnego indeksu tablicy w celu ustalenia, czy dwa bloki można połączyć. Korzystałem z tej funkcji, gdy opracowałem menedżera pamięci trwałej.
źródło
C nie jest mocno wpisany. Standardowy kompilator C nie sprawdzałby granic tablic. Inną rzeczą jest to, że tablica w C jest niczym innym jak ciągłym blokiem pamięci, a indeksowanie rozpoczyna się od 0, więc indeks -1 jest lokalizacją dowolnego wzorca bitów
a[0]
.Inne języki ładnie wykorzystują ujemne wskaźniki. W Pythonie
a[-1]
zwróci ostatni element,a[-2]
zwróci element przedostatni i tak dalej.źródło
int
,a[-5]
a zatem , bardziej ogólnie,int i; ... a[i] = ...;
są poprawnie wpisywane. Błędy indeksu są wykrywane tylko w czasie wykonywania. Oczywiście sprytny kompilator może wykryć niektóre naruszenia.W prostych słowach:
Wszystkie zmienne (w tym tablice) w C są przechowywane w pamięci. Załóżmy, że masz 14 bajtów „pamięci” i inicjujesz następujące:
Rozważ również wielkość int jako 2 bajty. Następnie hipotetycznie w pierwszych 2 bajtach zostanie zapisana liczba całkowita a. W następnych 2 bajtach zostanie zapisana liczba całkowita pierwszej pozycji tablicy (co oznacza tablicę [0]).
Następnie, gdy mówisz, że tablica [-1] przypomina odniesienie do liczby całkowitej zapisanej w pamięci, która znajduje się tuż przed tablicą [0], która w naszym przypadku jest hipotetycznie liczbą całkowitą a. W rzeczywistości nie jest to dokładnie sposób, w jaki zmienne są przechowywane w pamięci.
źródło
źródło