... po prostu zmniejszanie wskaźnika poza przydzielony zakres wydaje mi się bardzo szkicowe. Czy to „dozwolone” zachowanie w C?
Dozwolony? Tak. Dobry pomysł? Zazwyczaj nie.
C jest skrótem od języka asemblera, aw języku asemblera nie ma wskaźników, tylko adresy pamięci. Wskaźniki C są adresami pamięci, które mają boczne działanie polegające na zwiększaniu lub zmniejszaniu o rozmiar tego, na co wskazują, gdy są poddawane arytmetyki. To sprawia, że następujące elementy są w porządku z perspektywy składni:
double *p = (double *)0xdeadbeef;
--p; // p == 0xdeadbee7, assuming sizeof(double) == 8.
double d = p[0];
Tablice nie są tak naprawdę w C; są tylko wskaźnikami do ciągłych zakresów pamięci, które zachowują się jak tablice. []
Operator jest skrótem robi wskaźnik arytmetyczne i wyłuskania, tak a[x]
naprawdę oznacza *(a + x)
.
Istnieją uzasadnione powody, aby zrobić powyższe, na przykład niektóre urządzenia I / O mające kilka double
s mapowanych na 0xdeadbee7
i 0xdeadbeef
. Bardzo niewiele programów musiałoby to zrobić.
Kiedy tworzysz adres czegoś, na przykład za pomocą &
operatora lub połączenia malloc()
, chcesz zachować oryginalny wskaźnik nietknięty, abyś wiedział, że to, co wskazuje, jest w rzeczywistości czymś ważnym. Zmniejszenie wskaźnika oznacza, że trochę błędnego kodu może próbować go wyrejestrować, uzyskać błędne wyniki, zablokować coś lub, w zależności od środowiska, popełnić naruszenie segmentacji. Jest to szczególnie prawdziwe w przypadku malloc()
, ponieważ obciążysz każdego, kto dzwoni, free()
aby pamiętać o przekazaniu pierwotnej wartości, a nie jakiejkolwiek zmienionej wersji, która spowoduje, że wszyscy się uwolnią.
Jeśli potrzebujesz tablic opartych na 1 w C, możesz to zrobić bezpiecznie kosztem przydzielenia jednego dodatkowego elementu, który nigdy nie będzie używany:
double *array_create(size_t size) {
// Wasting one element, so don't allow it to be full-sized
assert(size < SIZE_MAX);
return malloc((size+1) * sizeof(double));
}
inline double array_index(double *array, size_t index) {
assert(array != NULL);
assert(index >= 1); // This is a 1-based array
return array[index];
}
Zauważ, że nie ma to nic wspólnego z ochroną przed przekroczeniem górnej granicy, ale jest to dość łatwe w obsłudze.
Uzupełnienie:
Trochę rozdziału i wersetu z projektu C99 (przepraszam, to wszystko, co mogę link do):
§6.5.2.1.1 mówi, że drugie („inne”) wyrażenie używane z operatorem indeksu dolnego jest liczbą całkowitą. -1
jest liczbą całkowitą, co czyni p[-1]
poprawność, a zatem również poprawia wskaźnik &(p[-1])
. Nie oznacza to, że dostęp do pamięci w tym miejscu spowodowałby określone zachowanie, ale wskaźnik jest nadal prawidłowym wskaźnikiem.
§6.5.2.2 mówi, że operator tablicy indeksów ocenia równoważność dodania numeru elementu do wskaźnika, a zatem p[-1]
jest równoważny *(p + (-1))
. Nadal obowiązuje, ale może nie powodować pożądanego zachowania.
W pkt 6.5.6.8 (podkreślenie moje):
Gdy wyrażenie, które ma typ liczb całkowitych, jest dodawane do wskaźnika lub odejmowane od niego, wynik ma typ argumentu wskaźnika.
... jeśli wyrażenie P
wskazuje na i
-ty element obiektu tablicowego, wyrażenia (P)+N
(równoważnie N+(P)
) i (P)-N
(gdzie N
ma wartość n
) wskazują odpowiednio na i+n
-ty i
i−n
-ty element obiektu tablicowego, pod warunkiem że istnieją .
Oznacza to, że wyniki arytmetyki wskaźnika muszą wskazywać element w tablicy. Nie mówi, że arytmetykę należy wykonać od razu. W związku z tym:
double a[20];
// This points to element 9 of a; behavior is defined.
double d = a[-1 + 10];
double *p = a - 1; // This is just a pointer. No dereferencing.
double e = p[0]; // Does not point at any element of a; behavior is undefined.
double f = p[1]; // Points at element 0 of a; behavior is defined.
Czy polecam robić to w ten sposób? Nie wiem, a moja odpowiedź wyjaśnia, dlaczego.