Dereferencing este ponteiro me dá -46, mas não tenho certeza porque

Este é um programa que eu corri:

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

Estou um pouco confuso sobre a saída. O que estou vendo é:

 -46 1234 

Eu escrevi este programa como um experimento e não tinha certeza do que iria produzir. Eu estava esperando possivelmente um byte de y .

O que está acontecendo “nos bastidores” aqui? Como a desreferenciação me dá -46 ?

Como apontado por outros, eu tive que fazer um casting explícito para não causar UB. Eu não estou mudando essa linha de char *p = &y; para char *p = (char *)&y; de modo que não estou invalidando as respostas abaixo.

Este programa não está causando nenhum comportamento de UB, como apontado aqui .

Se você tem algo parecido,

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

Se você desreferenciar o ponteiro p , ele irá ler corretamente os bytes inteiros. Porque você declarou que é um ponteiro para int . Ele saberá quantos bytes serão lidos pelo operador sizeof() . Geralmente, o tamanho de int é de 4 bytes (para plataformas de 32/64 bits), mas é dependente de máquina, por isso usará o operador sizeof() para saber o tamanho correto e assim o lerá.

Para o seu código

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

Agora, o pointer p aponta para y mas nós o declaramos como ponteiro para um char então ele só lerá um byte ou o que seja byte char. 1234 em binário seria representado como

00000000 00000000 00000100 11010010

Agora, se sua máquina for little endian, ela armazenará os bytes, invertendo-os

11010010 00000100 00000000 00000000

11010010 está no address 00 00000100 Hypothetical address , 00000100 está no address 01 e assim por diante.

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

Então, agora, se você desreferenciar o pointer p ele lerá somente o primeiro byte e a saída será ( -46 no caso de signed char e 210 no caso de unsigned char , de acordo com o padrão C, a assinatura do caractere simples é “implementação definida”. ) como Byte de leitura seria 11010010 (porque apontamos signed char (neste caso, é signed char ).

Em seu PC, os números negativos são representados como Complemento de 2, então o most-significant bit é o bit de sinal. Primeiro bit 1 denota o sinal. 11010010 = –128 + 64 + 16 + 2 = –46 e se você excluir o pointer j ele irá ler completamente todos os bytes do int como nós o declaramos como ponteiro para int e a saída será 1234

Se você declarar o ponteiro j como int *j então *j irá ler sizeof(int) aqui 4 bytes (dependente da máquina). O mesmo acontece com char ou qualquer outro tipo de dados que o ponteiro apontado para eles lerá quantos bytes o tamanho for de, char será de 1 byte.

Como outros apontaram, você precisa explicitamente converter para char* como char *p = &y; é uma violação de restrição – char * e int * não são tipos compatíveis, em vez disso, escreva char *p = (char *)&y .

Há alguns problemas com o código, conforme escrito.

Primeiro de tudo, você está invocando comportamento indefinido ao tentar imprimir a representação numérica de um object char usando o especificador de conversão %d :

Projeto on-line C 2011 , §7.21.6.1, subseção 9:

Se uma especificação de conversão for inválida, o comportamento é indefinido.282) Se algum argumento não for o tipo correto para a especificação de conversão correspondente, o comportamento é indefinido.

Sim, os objects do tipo char são promovidos para int quando passados ​​para funções variadicas; printf é especial, e se você deseja que a saída seja bem definida, então o tipo do argumento e o especificador de conversão devem corresponder. Para imprimir o valor numérico de um char com %d ou argumento unsigned char com %u , %o ou %x , você deve usar o modificador de comprimento hh como parte da especificação de conversão:

 printf( "%hhd ", *p ); 

A segunda questão é que a linha

 char *p = &y; 

é uma violação de restrição – char * e int * não são tipos compatíveis e podem ter diferentes tamanhos e / ou representações 2 . Portanto, você deve converter explicitamente a origem para o tipo de destino:

 char *p = (char *) &y; 

A única exceção a essa regra ocorre quando um dos operandos é void * ; então o casting não é necessário.

Tendo dito tudo isso, peguei seu código e adicionei um utilitário que despeja o endereço e o conteúdo dos objects no programa. Aqui está o aspecto de y , p e j no meu 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 .... 

Eu estou em um sistema x86, que é little-endian, então ele armazena objects de múltiplos bytes começando com o byte menos significativo no endereço mais baixo:

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

Em um sistema little-endian, o byte endereçado é o byte menos significativo, que neste caso é 0xd2 ( 210 não assinados, -46 assinado).

Em suma, você está imprimindo a representação decimal assinada desse único byte.

Quanto à questão mais ampla, o tipo da expressão *p é char e o tipo da expressão *j é int ; o compilador simplesmente atende pelo tipo da expressão. O compilador mantém o controle de todos os objects, expressões e tipos ao converter sua origem em código de máquina. Então, quando ele vê a expressão *j , ele sabe que está lidando com um valor inteiro e gera código de máquina apropriadamente. Quando ele vê a expressão *p , ele sabe que está lidando com um valor char .


  1. É verdade que quase todos os sistemas de desktop modernos que eu conheço usam as mesmas representações para todos os tipos de ponteiro, mas para plataformas mais estranhas incorporadas ou de propósito especial, isso pode não ser verdade.
  2. § 6.2.5, subcláusula 28.

(Por favor, note que esta resposta se refere à forma original da pergunta, que perguntou como o programa sabia quantos bytes para ler, etc. Eu estou mantendo em torno dessa base, apesar do tapete ter sido retirado de debaixo dele.)

Um ponteiro refere-se a um local na memory que contém um object específico e deve ser incrementado / decrementado / indexado com um determinado tamanho de passo, refletindo o tamanho do tipo apontado.

O valor observável do ponteiro em si (por exemplo, std::cout << ptr ) não precisa refletir nenhum endereço físico reconhecível, nem ++ptr precisa incrementar o dito valor em 1, sizeof(*ptr) , ou qualquer outra coisa. Um ponteiro é apenas um identificador para um object, com uma representação de bit definida pela implementação. Essa representação não é importante para os usuários. A única coisa que os usuários devem usar o ponteiro é ... bem, aponte para coisas. Falar de seu endereço não é portável e só é útil na debugging.

De qualquer forma, simplesmente, o compilador sabe quantos bytes para ler / escrever porque o ponteiro é typescript, e esse tipo tem um sizeof , representação e mapeamento definidos para os endereços físicos. Assim, com base nesse tipo, as operações no ptr serão compiladas em instruções apropriadas para calcular o endereço de hardware real (que, novamente, não precisa corresponder ao valor observável de ptr ), ler o sizeof correto do número de 'bytes' de memory, adicione / subtraia o número certo de bytes para que aponte para o próximo object, etc.

Primeiro leia o aviso que diz aviso: boot do tipo de ponteiro incompatível [ativado por padrão] char * p = & y;

o que significa que você deve fazer typecasting explícito para evitar o comportamento indefinido de acordo com a norma §7.21.6.1, subcláusula 9 (apontada por @john Bode) como

 chat *p = (char*)&y; 

e

 int y =1234; 

aqui y é a local variable e será armazenada na seção de stack da RAM . Os números inteiros da máquina Linux são armazenados na memory de acordo com o formato little endian . Suponha que 4 bytes de memory reservados para y sejam de 0x100 a 0x104

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

Como apontado acima, j e p apontam para o mesmo endereço 0x100 mas quando o compilador executa *p já que p é um signed character pointer por padrão, ele verifica sign bit e aqui o sign bit é 1 significa que uma coisa é certa é um número negativo .

Se o sign bit é 1 ou seja, o número negativo e os números negativos são armazenados na memory como um complemento do 2

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

durante a impressão, se você estiver usando o especificador de formato %u , que é para a impressão de unsigned equivalent, ele not irá marcar o sign bi t, finalmente, quaisquer dados que estiverem lá em 1 byte serão impressos.

finalmente

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

Na declaração acima, enquanto fazendo dereferencing j que é signed pointer por padrão e é um ponteiro int então ele irá verificar 31º bit para sinal , que é 0 significa saída será positive não e que é 1234.