Indicizzazione del puntatore

11

Attualmente sto leggendo un libro intitolato "Numerical Recipes in C". In questo libro, l'autore descrive in dettaglio come alcuni algoritmi funzionano intrinsecamente meglio se abbiamo indici che iniziano con 1 (non seguo completamente le sue argomentazioni e non è questo il punto di questo post), ma C indicizza sempre i suoi array a partire da 0 Per evitare questo, suggerisce semplicemente di decrementare il puntatore dopo l'allocazione, ad esempio:

float *a = malloc(size);
a--;

Questo, dice, ti darà effettivamente un puntatore che ha un indice che inizia con 1, che verrà quindi liberato con:

free(a + 1);

Per quanto ne so, però, questo è un comportamento non definito dallo standard C. Questo è apparentemente un libro altamente rispettabile all'interno della comunità HPC, quindi non voglio semplicemente ignorare ciò che sta dicendo, ma semplicemente decrementare un puntatore al di fuori dell'intervallo assegnato mi sembra molto impreciso. Questo comportamento "permesso" è in C? L'ho provato usando sia gcc che icc, e entrambi i risultati sembrano indicare che non mi preoccupo per nulla, ma voglio essere assolutamente positivo.

    
posta wolfPack88 08.07.2014 - 14:48
fonte

4 risposte

16

Hai ragione quel codice come

float a = malloc(size);
a--;

produce un comportamento indefinito, secondo lo standard ANSI C, sezione 3.3.6:

Unless both the pointer operand and the result point to a member of the same array object, or one past the last member of the array object, the behavior is undefined

Per un codice come questo, la qualità del codice C nel libro (quando la usavo alla fine degli anni '90) non era considerata molto alta.

Il problema con un comportamento indefinito è che indipendentemente dal risultato del compilatore, il risultato è per definizione corretto (anche se è altamente distruttivo e imprevedibile).
Fortunatamente, pochissimi compilatori si sforzano di causare effettivamente un comportamento inaspettato per questi casi e l'implementazione tipica malloc sulle macchine utilizzate per HPC ha alcuni dati contabili poco prima che l'indirizzo ritorni, quindi il decremento tipicamente ti darà un puntatore dati contabili. Non è una buona idea scrivere lì, ma solo creare il puntatore è innocuo su quei sistemi.

Tieni presente che il codice potrebbe interrompersi quando l'ambiente di runtime viene modificato o quando il codice viene trasferito in un ambiente diverso.

    
risposta data 08.07.2014 - 15:13
fonte
10

Ufficialmente, è un comportamento indefinito avere un puntatore al di fuori dell'array (eccetto uno alla fine), anche se non è mai dereferenziato .

In pratica, se il tuo processore ha un modello di memoria piatto (al contrario di quelli bari come x86- 16 ), e se il compilatore non ti dà un errore di runtime o un'ottimizzazione errata se crei un puntatore non valido, allora il codice funzionerà bene.

    
risposta data 08.07.2014 - 15:32
fonte
3

In primo luogo, è un comportamento indefinito. Alcuni compilatori ottimizzanti oggigiorno diventano molto aggressivi riguardo al comportamento non definito. Ad esempio, poiché a-- in questo caso è un comportamento indefinito, il compilatore potrebbe decidere di salvare un'istruzione e un ciclo del processore e non decrementare a. Che è ufficialmente corretto e legale.

Ignorandolo, potresti sottrarre 1, o 2 o 1980. Ad esempio, se dispongo di dati finanziari per gli anni dal 1980 al 2013, potrei sottrarre 1980. Ora se prendiamo float * a = malloc (dimensione); c'è sicuramente alcuni grande costante k tale che a -k è un puntatore nullo. In tal caso, ci aspettiamo davvero che qualcosa vada storto.

Ora prendi una grande struttura, ad esempio una dimensione di megabyte. Assegna un puntatore p che punta a due strutture. p - 1 potrebbe essere un puntatore nullo. p - 1 potrebbe avvolgere (se una struct è un megabyte, e il blocco malloc è 900 KB dall'inizio dello spazio degli indirizzi). Quindi potrebbe essere senza alcuna malizia del compilatore che p - 1 > p. Le cose potrebbero diventare interessanti.

    
risposta data 08.07.2014 - 20:22
fonte
1

...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é.

    
risposta data 08.07.2014 - 15:36
fonte

Leggi altre domande sui tag