...simply decrementing a pointer outside of the allocated range seems highly sketchy to me. Is this "allowed" behavior in C?
ammessi? Sì. Buona idea? Di solito.
C è una scorciatoia per il linguaggio assembly, e nel linguaggio assembly non ci sono puntatori, solo indirizzi di memoria. I puntatori di C sono indirizzi di memoria che hanno un comportamento collaterale di incremento o decremento delle dimensioni di ciò a cui puntano quando sottoposti all'aritmetica. Ciò rende bene quanto segue da una prospettiva di sintassi:
double *p = (double *)0xdeadbeef;
--p; // p == 0xdeadbee7, assuming sizeof(double) == 8.
double d = p[0];
Gli array non sono davvero una cosa in C; sono solo indicatori di intervalli contigui di memoria che si comportano come array. L'operatore []
è una scorciatoia per eseguire aritmetica e dereferenziazione del puntatore, quindi a[x]
significa effettivamente *(a + x)
.
Ci sono validi motivi per fare quanto sopra, come ad esempio un dispositivo I / O che ha un paio di double
s mappato in 0xdeadbee7
e 0xdeadbeef
. Pochi programmi avrebbero bisogno di farlo.
Quando crei l'indirizzo di qualcosa, ad esempio utilizzando l'operatore &
o chiamando malloc()
, vuoi mantenere intatto il puntatore originale in modo che tu sappia che ciò a cui punta è in realtà qualcosa di valido. Decrementare il puntatore significa che un po 'di codice errante potrebbe provare a dereferenziarlo, ottenendo risultati errati, rovinando qualcosa o, a seconda dell'ambiente, commettendo una violazione di segmentazione. Questo è particolarmente vero con malloc()
, perché hai messo il peso su chi ha chiamato free()
a ricordare di passare il valore originale e non una versione alterata che causerà la rottura di tutti i diametri.
Se hai bisogno di array basati su 1 in C, puoi farlo in modo sicuro 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 di la bozza C99 (mi dispiace, questo è tutto ciò che posso collegare a):
§6.5.2.1.1 dice che la seconda espressione ("altro") usata con l'operatore subscript è di tipo intero. -1
è un numero intero che rende p[-1]
valido e pertanto rende anche il puntatore &(p[-1])
valido. 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 pedice di array valuta l'equivalente di aggiungere il numero di elemento al puntatore, quindi p[-1]
equivale a *(p + (-1))
. Ancora valido, ma potrebbe non produrre un comportamento desiderabile.
§6.5.6.8 dice (sottolineatura mia):
When an expression that has integer type is added to or subtracted from a pointer, the result has the type of the pointer operand.
...if the expression P
points to the i
-th element of an
array object, the expressions (P)+N
(equivalently, N+(P)
) and (P)-N
(where N
has the value n
) point to, respectively, the i+n
-th and
i−n
-th elements of the array object, provided they exist.
Ciò significa che i risultati dell'aritmetica del puntatore devono puntare a un elemento in una matrice. Non dice che l'aritmetica deve essere fatta tutto in una volta. Pertanto:
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.
Raccomando di fare le cose in questo modo? Io no, e la mia risposta spiega perché.