... semplicemente decrementare un puntatore al di fuori dell'intervallo assegnato mi sembra molto impreciso. Questo comportamento è "permesso" in C?
Permesso? Sì. Buona idea? Non solitamente.
C è una scorciatoia per il linguaggio assembly e nel linguaggio assembly non ci sono puntatori, ma solo indirizzi di memoria. I puntatori di C sono indirizzi di memoria che hanno un comportamento laterale di incremento o decremento in base alla dimensione di ciò a cui indicano quando sottoposti all'aritmetica. Questo rende quanto segue bene dal punto di vista della sintassi:
double *p = (double *)0xdeadbeef;
--p; // p == 0xdeadbee7, assuming sizeof(double) == 8.
double d = p[0];
Le matrici non sono davvero una cosa in C; sono solo puntatori a intervalli contigui di memoria che si comportano come array. L' []
operatore è una scorciatoia per eseguire l'aritmetica e la dereferenziazione del puntatore, quindi in a[x]
realtà significa *(a + x)
.
Esistono validi motivi per fare quanto sopra, come alcuni dispositivi I / O con un paio di double
s mappati in 0xdeadbee7
e 0xdeadbeef
. Pochissimi programmi dovrebbero farlo.
Quando si crea l'indirizzo di qualcosa, ad esempio utilizzando l' &
operatore o chiamando malloc()
, si desidera mantenere intatto il puntatore originale in modo da sapere che ciò a cui punta è effettivamente qualcosa di valido. Decrementare il puntatore significa che un po 'di codice errato potrebbe tentare di dereferenziarlo, ottenendo risultati errati, ostruendo qualcosa o, a seconda del proprio ambiente, commettendo una violazione della segmentazione. Questo è particolarmente vero con malloc()
, perché hai messo l'onere su chi sta chiamando free()
a ricordare di passare il valore originale e non una versione alterata che farà perdere tutto il diavolo.
Se hai bisogno di array basati su 1 in C, puoi farlo in sicurezza a spese dell'allocazione di un elemento aggiuntivo che non verrà mai utilizzato:
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];
}
Nota che questo non fa nulla per proteggere dal superamento del limite superiore, ma è abbastanza facile da gestire.
Addendum:
Alcuni capitoli e versetti della bozza del C99 (scusate, è tutto ciò a cui posso collegarmi):
§6.5.2.1.1 afferma che la seconda ("altra") espressione utilizzata con l'operatore di pedice è di tipo intero. -1
è un numero intero, che rende p[-1]
valido e quindi rende valido anche il puntatore &(p[-1])
. Ciò non implica che l'accesso alla memoria in quella posizione produrrebbe un comportamento definito, ma il puntatore è ancora un puntatore valido.
§6.5.2.2 afferma che l'operatore di sottoscrizione dell'array valuta l'equivalente dell'aggiunta del numero di elemento al puntatore, pertanto p[-1]
è equivalente a *(p + (-1))
. È ancora valido, ma potrebbe non produrre comportamenti desiderabili.
§6.5.6.8 dice (enfasi mia):
Quando un'espressione con tipo intero viene aggiunta o sottratta da un puntatore, il risultato ha il tipo di operando puntatore.
... se l'espressione P
punta i
all'elemento -th di un oggetto array, le espressioni (P)+N
(equivalentemente N+(P)
) e (P)-N
(dove N
ha il valore n
) puntano, rispettivamente, agli elementi i+n
-th e
i−n
-th dell'oggetto array, purché esistano .
Ciò significa che i risultati dell'aritmetica del puntatore devono puntare a un elemento in un array. Non dice che l'aritmetica debba essere fatta tutta in una volta. Perciò:
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.
Consiglio di fare le cose in questo modo? Non lo so, e la mia risposta spiega perché.