2014-09-04 9 views
6

Qualcuno ha scritto il seguente programma C e ha chiesto perché gcc consente "di modificare l'indirizzo di base di un array". Era consapevole del fatto che il codice era terribile ma voleva ancora sapere. Ho trovato la domanda abbastanza interessante, perché la relazione tra array e puntatori in C è sottile (ecco l'uso dell'operatore di indirizzo sull'array! "Perché qualcuno dovrebbe farlo?"), Confondendo e di conseguenza spesso frainteso. La domanda è stata cancellata, ma ho pensato di chiederlo di nuovo, con un contesto appropriato e - come spero - una risposta adeguata ad esso. Ecco il prog originalePossiamo cambiare l'indirizzo di base di un array attraverso un puntatore all'array usando la forza bruta?

Si compila con gcc (con avvisi), collegamenti e corse. Che succede qui? Possiamo cambiare l'indirizzo di base di un array prendendo il suo indirizzo, gettandolo al puntatore al puntatore (dopo che tutti gli array sono quasi puntatori in C, non lo sono) e assegnandoli?

+0

per i posteri, [qui] (http://stackoverflow.com/questions/25660493/assegnazione-per-a-char-array-using-assignment-operator-and-double-pointers) è la domanda cancellata –

risposta

4

Il programma non modifica l '"indirizzo di base" dell'array. Non ci sta nemmeno provando.

Quello che si passa a fn è l'indirizzo di un blocco di 256 caratteri in memoria. È numericamente identico al puntatore che decodifica str in altre espressioni, solo in modo diverso tipizzato. In questo caso, la matrice rimane davvero un array: l'applicazione dell'operatore di indirizzo a un array è una delle istanze in cui un array non decompone a un puntatore. L'incremento &str, ad esempio, lo aumenterebbe numericamente di 256. Questo è importante per gli array multidimensionali che, come sappiamo, sono in realtà matrici unidimensionali di matrici in C. L'incremento del primo indice di una matrice "bidimensionale" deve anticipare l'indirizzo all'inizio del successivo "blocco" o "riga".

Ora il problema. Per quanto riguarda fn, l'indirizzo che passi punta a una posizione che contiene un altro indirizzo. Quello non è vero; indica una sequenza di caratteri. La stampa della sequenza di byte interpretata come un puntatore rivela i valori del byte 'A', 65 o 0x41.

fn, tuttavia, pensando che la memoria puntata a contenere un indirizzo, la sovrascriva con l'indirizzo in cui "kj" risiede nella memoria. Poiché c'è abbastanza memoria allocata in str per contenere un indirizzo, l'assegnazione ha esito positivo e si traduce in un indirizzo utilizzabile in quella posizione.

Va notato che questo, ovviamente, non è garantito per funzionare. La causa più comune di errore dovrebbe essere problemi di allineamento - str non è necessario che sia allineato correttamente per un valore puntatore. Lo standard impone che gli argomenti alle funzioni debbano essere compatibili con le dichiarazioni dei parametri. I tipi di puntatori arbitrari non possono essere assegnati l'uno all'altro (è necessario passare attraverso i puntatori void per quello o il cast).

Edit: david.pfx sottolineato che (anche con una vera e propria fusione) il codice invoca comportamento non definito. Lo standard richiede l'accesso agli oggetti tramite lvalue compatibili (compresi i puntatori referenziati) nella sezione 6.5/7 dell'ultima bozza pubblica. Quando si esegue correttamente il cast e si compila con gcc -fstrict-aliasing -Wstrict-aliasing=2 ... gcc avvisa del "tipo punning". La logica è che il compilatore dovrebbe essere libero di assumere che i puntatori incompatibili non modificano la stessa regione di memoria; qui non è necessario assumere che fn cambi il contenuto di str. Ciò consente al compilatore di ottimizzare i ricaricamenti (ad esempio dalla memoria al registro) che sarebbero altrimenti necessari. Questo giocherà un ruolo con l'ottimizzazione; un probabile esempio in cui una sessione di debug non riuscirebbe a riprodurre l'errore (vale a dire se il programma in fase di debug verrebbe compilato senza ottimizzazione per scopi di debug). Detto questo, sarei sorpreso se un compilatore non ottimizzante producesse risultati inaspettati qui, quindi lascio che il resto della risposta resti valido.-

Ho inserito un numero di debug printfs per illustrare cosa sta succedendo. Un esempio dal vivo può essere visto qui: http://ideone.com/aL407L.

#include<stdio.h> 
#include<string.h> 
static char* abc = "kj"; 

// Helper function to print the first bytes a char pointer points to 
void printBytes(const char *const caption, const char *const ptr) 
{ 
    int i=0; 
    printf("%s: {", caption); 
    for(i=0; i<sizeof(char *)-1; ++i) 
    { 
     printf("0x%x,", ptr[i]); 
    } 
    printf("0x%x ...}\n", ptr[sizeof(char *)-1]); 
} 

// What exactly does this function do? 
void fn(char**s) { 
    printf("Inside fn: Argument value is %p\n", s); 
    printBytes("Inside fn: Bytes at address above are", (char *)s); 

    // This throws. *s is not a valid address. 
    // printf("contents: ->%s<-\n", *s); 

    *s = abc; 
    printf("Inside fn: Bytes at address above after assignment\n"); 
    printBytes("   (should be address of \"kj\")", (char *)s); 

    // Now *s holds a valid address (that of "kj"). 
    printf("Inside fn: Printing *s as string (should be kj): ->%s<-\n", *s); 

} 


int main() { 
    char str[256]; 

    printf("size of ptr: %zu\n", sizeof(void *)); 
    strcpy(str, "AAAAAAAA"); // 9 defined bytes 

    printf("addr of \"kj\": %p\n", abc); 
    printf("str addr: %p (%p)\n", &str, str); 
    printBytes("str contents before fn", str); 

    printf("------------------------------\n"); 
    // Paramter type does not match! Illegal code 
    // (6.5.16.1 of the latest public draft; incompatible 
    // types for assignment). 
    fn(&str); 

    printf("------------------------------\n"); 

    printBytes("str contents after fn (i.e. abc -- note byte order!): ", str); 
    printf("str addr after fn -- still the same! --: %p (%p)\n", &str, str); 

    return 0; 
} 
+2

I chiedo come eri in tempo per rispondere con un post così lungo quando la domanda è stata posta 6 minuti fa e la tua risposta è apparsa anche 6 minuti fa? !!! Per inserire solo il codice del programma ci vorrà più di 1 minuto. –

+1

Bene, Ctrl-V impiega 1/100 ;-). E ho rilevato che quando faccio una domanda, SO mi dà l'opportunità di scrivere subito la risposta insieme alla domanda. Questo modello di auto-Q/A è uno dei formati supportati per SO in modo che le persone possano condividere le proprie intuizioni (non si può scrivere una risposta senza una domanda, penso). Questa è la prima volta che ci provo. –

+0

@VladfromMoscow questo è stato copiato da un'altra domanda che è stata cancellata –

5

Non può funzionare (anche teoricamente), perché gli array non sono puntatori:


  • int arr[10]:

    • quantità di memoria utilizzata è sizeof(int)*10 byte

    • I valori di arr e &arr sono necessariamente identici

    • arr punti ad un indirizzo di memoria valido, ma non possono essere impostati per puntare a un altro indirizzo di memoria


  • int* ptr = malloc(sizeof(int)*10):

    • quantità di memoria utilizzata è sizeof(int*) + sizeof(int)*10 byte

    • I valori di ptr e &ptr non sono necessariamente identici (infatti, essi sono per lo più diversi)

    • ptr può essere impostato per indicare sia indirizzi validi e non validi di memoria, tutte le volte che si vuole

+0

Beh, l'O-OP (non me) è rimasto perplesso dal fatto che puoi prendere l'indirizzo dell'array, dereferenziarlo e assegnarlo (dopo averlo lanciato efficacemente). Si aspettava che ciò avrebbe cambiato il "valore" come con altri puntatori, come in '(* (& x)) = 1'. Dato che quel "valore" era la matrice che pensava di cambiare la matrice, in qualche modo (non il suo contenuto). Nella mia risposta ho sezionato l'effetto di un operatore di indirizzo applicato a un array, l'interpretazione del valore dell'argomento risultante all'interno di 'fn' dopo la conversione del tipo e gli effetti sui valori coinvolti. –

+0

@PeterSchneider: Mi hai lasciato perplesso qui. Ti ho dato un +1 perché ho trovato la domanda interessante. Poi ho notato che anche tu eri quello che ha risposto (dal primo commento alla risposta), che è un po 'strano, ma va bene per quanto mi riguarda ... In ogni caso, ora stai dicendo "O-OP (non me) è stato perplesso". Cosa significa "O-OP" e perché ti riferisci a te stesso come "non io" ??? –

+0

Ho usato il formato "rispondi alla tua domanda" per diffondere la saggezza ;-). Non è così raro; SO in realtà fornisce un'opzione per modificare la tua risposta insieme alla domanda, di cui non ero a conoscenza fino ad oggi. Questo fa apparire prima la risposta dell'OP stesso. - Come ho spiegato nella domanda, si basa su uno che un utente diverso ha chiesto e poi cancellato. Quella persona sarebbe stata la "Original Original Poster", o O-OP. Non sono stato io. Sono l'OP in questa discussione. –

2

Quello che hai qui è semplicemente un comportamento indefinito.

Il parametro della funzione è dichiarato come puntatore-a-puntatore-a-carattere. L'argomento passato ad esso è pointer-to-array-of-256-char. Lo standard consente conversioni tra un puntatore e un altro, ma poiché l'oggetto a cui punta non è un puntatore a carattere, il dereferenziamento del puntatore è Comportamento indefinito.

n1570 S6.5.3.2/4:

Se un valore non valido è stato assegnato al puntatore, il comportamento del unario * operatore è indefinito.

È inutile speculare su come si svolgerà il comportamento indefinito su diverse implementazioni. È semplicemente sbagliato.


Proprio per intenderci, UB è in questa linea:

*s=abc; 

Il puntatore s non punta ad un oggetto del tipo corretto (char*), quindi l'uso di * è UB .

+0

Non penso sia UB (beh, gcc ci fa passare il tipo di puntatore sbagliato che a rigor di termini richiederebbe un cast, ma assumiamo un cast quando chiamiamo 'fn()'). Il valore passato non è valido; è un puntatore valido per l'array. Il dereferenziamento è ben definito, se i requisiti di allineamento sono soddisfatti. Cf n1570, 6.3.2.3/7: "Un puntatore a un tipo di oggetto può essere [esplicitamente, P.S.] convertito in un puntatore a un tipo di oggetto diverso." Il paragrafo quindi discute i problemi di allineamento e il comportamento durante la conversione in char *, ma non limita il tipo di puntatore di destinazione. –

+0

Si noti inoltre che il codice non dereferenzia mai '* s' (ad es. Tenta di leggere' ** s') che sarebbe illegale e si blocca su sistemi con protezione della memoria, almeno prima che 'abc' fosse assegnato a' * s'. –

+1

@PeterSchneider: '* s = abc' è UB secondo lo standard. Non importa se lo chiami "dereferenze" o no (io lo faccio). Vedi modifica. –

Problemi correlati