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