Dereferenziare questo puntatore mi dà -46, ma non sono sicuro del perché

Questo è un programma che ho eseguito:

#include  int main(void) { int y = 1234; char *p = &y; int *j = &y; printf("%d %d\n", *p, *j); } 

Sono un po ‘confuso sull’output. Quello che sto vedendo è:

 -46 1234 

Ho scritto questo programma come un esperimento e non ero sicuro di cosa avrebbe prodotto. Mi aspettavo forse un byte da y .

Cosa sta succedendo “dietro le quinte” qui? In che modo mi dà la dereferenziazione -46 ?

Come sottolineato da altri, ho dovuto fare il casting esplicito per non causare UB. Non sto cambiando quella riga da char *p = &y; char *p = (char *)&y; in modo da non invalidare le risposte di seguito.

Questo programma non sta causando alcun comportamento UB come indicato qui .

    Se hai qualcosa del genere,

     int x = 1234; int *p = &x; 

    Se You Dereference Pointer p allora leggerà correttamente i byte interi. Perché hai dichiarato di essere puntatore a int . Saprà quanti byte leggere dall’operatore sizeof() . Generalmente la dimensione di int è di 4 bytes (per piattaforms a 32/64 bit) ma dipende dalla macchina, per questo motivo userà l’operatore sizeof() per conoscere la dimensione corretta e leggerà così.

    Per il tuo codice

      int y = 1234; char *p = &y; int *j = &y; 

    Ora il pointer p punta a y ma abbiamo dichiarato che è puntatore a un char quindi leggerà solo un byte o qualunque sia il byte char. 1234 in binario sarebbe rappresentato come

    00000000 00000000 00000100 11010010

    Ora se la tua macchina è little endian, memorizzerà i byte invertendoli

    11010010 00000100 00000000 00000000

    11010010 è address 00 Hypothetical address , 00000100 è address 01 e così via.

     BE: 00 01 02 03 +----+----+----+----+ y: | 00 | 00 | 04 | d2 | +----+----+----+----+ LE: 00 01 02 03 +----+----+----+----+ y: | d2 | 04 | 00 | 00 | +----+----+----+----+ (In Hexadecimal) 

    Quindi, se si denota il pointer p , verrà letto solo il primo byte e l’output sarà ( -46 in caso di signed char e 210 in caso di unsigned char , in base allo standard C la firma di plain char è “implementazione definita”. ) come letto dovrebbe essere 11010010 (perché abbiamo indicato il signed char (in questo caso è signed char ).

    Sul PC i numeri negativi sono rappresentati come complemento a 2, quindi il most-significant bit è il bit di segno. Il primo bit 1 indica il segno. 11010010 = –128 + 64 + 16 + 2 = –46 e se si denota il pointer j esso leggerà completamente tutti i byte di int come abbiamo dichiarato che sia puntatore a int e l’output sarà 1234

    Se dichiari il puntatore j come int *j *j leggerà sizeof(int) qui 4 byte (dipendente dalla macchina). Lo stesso vale per char o qualsiasi altro tipo di dati, il puntatore a cui è indirizzato leggerà quanti byte ha la dimensione, char è di 1 byte.

    Come altri hanno sottolineato, è necessario eseguire il cast esplicito su char* come char *p = &y; è una violazione del vincolo – char * e int * non sono tipi compatibili, invece scrivi char *p = (char *)&y .

    Ci sono un paio di problemi con il codice come scritto.

    Prima di tutto, stai invocando un comportamento indefinito cercando di stampare la rappresentazione numerica di un object char usando lo strumento di conversione %d :

    Bozza online C 2011 , §7.21.6.1, punto 9:

    Se una specifica di conversione non è valida, il comportamento non è definito.282) Se un argomento non è il tipo corretto per la specifica di conversione corrispondente, il comportamento non è definito.

    Sì, gli oggetti di tipo char vengono promossi a int quando vengono passati a funzioni variadic; printf è speciale e se vuoi che l’output sia ben definito, allora il tipo di argomento e lo specificatore di conversione devono coincidere. Per stampare il valore numerico di un char con %d o argomento unsigned char con %u , %o , o %x , devi utilizzare il modificatore di lunghezza hh come parte della specifica di conversione:

     printf( "%hhd ", *p ); 

    Il secondo problema è che la linea

     char *p = &y; 

    è una violazione del vincolo – char * e int * non sono tipi compatibili e possono avere dimensioni e / o rappresentazioni differenti 2 . Pertanto, è necessario trasmettere esplicitamente l’origine al tipo di destinazione:

     char *p = (char *) &y; 

    L’ unica eccezione a questa regola si verifica quando uno degli operandi è void * ; quindi il cast non è necessario.

    Detto questo, ho preso il tuo codice e ho aggiunto un’utilità che scarica l’indirizzo e il contenuto degli oggetti nel programma. Ecco come appaiono y , p e j sul mio sistema (SLES-10, gcc 4.1.2):

      Item Address 00 01 02 03 ---- ------- -- -- -- -- y 0x7fff1a7e99cc d2 04 00 00 .... p 0x7fff1a7e99c0 cc 99 7e 1a ..~. 0x7fff1a7e99c4 ff 7f 00 00 .... j 0x7fff1a7e99b8 cc 99 7e 1a ..~. 0x7fff1a7e99bc ff 7f 00 00 .... 

    Sono su un sistema x86, che è little-endian, quindi memorizza oggetti multi-byte che iniziano con il byte meno significativo all’indirizzo più basso:

     BE: A A+1 A+2 A+3 +----+----+----+----+ y: | 00 | 00 | 04 | d2 | +----+----+----+----+ LE: A+3 A+2 A+1 A 

    Su un sistema little-endian, il byte indirizzato è il byte meno significativo, che in questo caso è 0xd2 ( 210 senza segno, -46 firmato).

    In poche parole, stai stampando la rappresentazione decimale firmata di quel singolo byte.

    Per quanto riguarda la domanda più ampia, il tipo dell’espressione *p è char e il tipo dell’espressione *j è int ; il compilatore passa semplicemente dal tipo di espressione. Il compilatore tiene traccia di tutti gli oggetti, espressioni e tipi mentre traduce il codice sorgente in codice macchina. Quindi, quando vede l’espressione *j , sa che si tratta di un valore intero e genera codice macchina in modo appropriato. Quando vede l’espressione *p , sa che ha a che fare con un valore char .


    1. Certo, quasi tutti i moderni sistemi desktop che conosco usano le stesse rappresentazioni per tutti i tipi di puntatore, ma per più piattaforms embedded o per scopi speciali, potrebbe non essere vero.
    2. § 6.2.5, sottopunto 28.

    (Si noti che questa risposta si riferisce alla forma originale della domanda, che chiedeva come il programma sapesse quanti byte leggere, ecc. Lo tengo in giro su questa base, nonostante il tappeto sia stato estratto da sotto di esso.)

    Un puntatore fa riferimento a una posizione in memoria che contiene un object particolare e deve essere incrementata / decrementata / indicizzata con una particolare dimensione della falcata, che riflette la dimensione del tipo a punta.

    Il valore osservabile del puntatore stesso (ad esempio attraverso std::cout < < ptr ) non deve riflettere alcun indirizzo fisico riconoscibile, né ++ptr bisogno di incrementare il valore di 1, sizeof(*ptr) o qualsiasi altra cosa. Un puntatore è solo un handle per un object, con una rappresentazione di bit definita dall'implementazione. Questa rappresentazione non ha e non dovrebbe importare agli utenti. L'unica cosa per cui gli utenti dovrebbero usare il puntatore è ... beh, indicare cose. Parlare del suo indirizzo è non portabile e utile solo nel debug.

    Ad ogni modo, semplicemente, il compilatore conosce quanti byte leggere / scrivere perché il puntatore è digitato e quel tipo ha una dimensione definita, una rappresentazione e un mapping agli indirizzi fisici. Quindi, in base a quel tipo, le operazioni su ptr verranno compilate in modo appropriato per calcolare l'indirizzo hardware reale (che ancora non deve corrispondere al valore osservabile di ptr ), leggere la giusta dimensione del numero di byte di memoria, aggiungi / sottrai il giusto numero di byte in modo che punti all'object successivo, ecc.

    Prima leggi l’avvertenza che dice avvertimento: inizializzazione dal tipo di puntatore incompatibile [abilitato di default] char * p = & y;

    il che significa che dovresti fare un typecasting esplicito per evitare comportamenti non definiti secondo lo standard §7.21.6.1, punto 9 (indicato da @john Bode) come

     chat *p = (char*)&y; 

    e

     int y =1234; 

    qui y è la local variable e sarà memorizzata nella sezione stack della RAM . In Linux i numeri interi della macchina sono memorizzati in memoria secondo il formato little endian . Si supponga che 4 bytes di memoria riservati per y siano da 0x100 a 0x104

      ------------------------------------------------- | 0000 0000 | 0000 0000 | 0000 0100 | 1101 0010 | ------------------------------------------------- 0x104 0x103 0x102 0x101 0x100 y p j 

    Come indicato sopra, j e p puntano entrambi allo stesso indirizzo 0x100 ma quando il compilatore eseguirà *p poiché p è signed character pointer con sign bit di default, controllerà il sign bit e qui il sign bit è 1 significa che una cosa è sicura che l’output verrà stampato è un numero negativo

    Se il sign bit è 1 cioè il numero negativo e i numeri negativi sono memorizzati in memoria come complimento di 2 così

      actual => 1101 0010 (1st byte) ones compliment => 0010 1101 +1 ------------ 0010 1110 => 46 and since sign bit was one it will print -46 

    durante la stampa se si utilizza l’ %u formato %u , che è per la stampa di equivalente unsigned , not controllerà il sign bi , infine qualsiasi dato ci sia in 1 byte viene stampato.

    finalmente

     printf("%d\n",*j); 

    Nell’istruzione precedente mentre si fa il dereferenziamento j che è il signed pointer per impostazione predefinita e il suo puntatore int quindi controllerà il 31 ° bit per il segno , che è 0 significa che l’uscita sarà positive no e che è 1234.