Desreferenciando este puntero me da -46, pero no estoy seguro de por qué

Este es un progtwig que ejecuté:

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

Estoy un poco confundido sobre la salida. Lo que estoy viendo es:

 -46 1234 

Escribí este progtwig como un experimento y no estaba seguro de qué iba a salir. Esperaba posiblemente un byte de y .

¿Qué está sucediendo “detrás de escena” aquí? ¿Cómo me puede quitar dereferencia p -46 ?

Como señalaron otros, tuve que hacer un casting explícito para no causar UB. No estoy cambiando esa línea de char *p = &y; char *p = (char *)&y; para que no invalide las respuestas a continuación.

Este progtwig no está causando ningún comportamiento UB como se señala aquí .

Si tienes algo así como,

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

Si aprueba el puntero p , leerá correctamente los bytes enteros. Porque has declarado que es un puntero a int . Sabrá cuántos bytes leer por el operador sizeof() . Generalmente, el tamaño de int es de 4 bytes (para plataformas de 32/64 bits) pero depende de la máquina, por eso utilizará el operador sizeof() para conocer el tamaño correcto y lo leerá.

Para su código

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

Ahora el pointer p apunta a y pero hemos declarado que es un puntero a un char por lo que solo leerá un byte o lo que sea de byte char. 1234 en binario se representaría como

00000000 00000000 00000100 11010010

Ahora, si su máquina es pequeña endian, almacenará los bytes revertiéndola

11010010 00000100 00000000 00000000

11010010 está en la address 00 Hypothetical address , 00000100 está en la address 01 y así sucesivamente.

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

Entonces, si desreferencia el pointer p , leerá solo el primer byte y la salida será ( -46 en el caso de signed char y 210 en el caso de unsigned char , según el estándar C, el carácter firmado de plain char es “implementación definida”. ) Byte read sería 11010010 (porque 11010010 signed char (en este caso está signed char ).

En su PC, los números negativos se representan como Complemento 2, por lo que el most-significant bit es el bit de signo. El primer bit 1 denota el signo. 11010010 = –128 + 64 + 16 + 2 = –46 y si desreferencia el pointer j leerá completamente todos los bytes de int ya que lo declaramos como puntero a int y la salida será 1234

Si declara el puntero j como int *j entonces *j leerá sizeof(int) aquí 4 bytes (depende de la máquina). Lo mismo ocurre con char o cualquier otro tipo de datos que el puntero apunte a ellos leerá tantos bytes hay de tamaño, char es de 1 byte.

Como han señalado otros, debe emitir explícitamente a char* como char *p = &y; es una violación de restricción – char * e int * no son tipos compatibles, en su lugar escribe char *p = (char *)&y .

Hay un par de problemas con el código tal como está escrito.

En primer lugar, está invocando un comportamiento indefinido al intentar imprimir la representación numérica de un objeto char utilizando el especificador de conversión %d :

Borrador en línea C 2011 , §7.21.6.1, subcláusula 9:

Si una especificación de conversión no es válida, el comportamiento no está definido.282) Si un argumento no es del tipo correcto para la especificación de conversión correspondiente, el comportamiento no está definido.

Sí, los objetos de tipo char se promueven a int cuando se pasan a funciones variadas; printf es especial, y si desea que la salida esté bien definida, entonces el tipo de argumento y el especificador de conversión deben coincidir. Para imprimir el valor numérico de una char con %d o argumento unsigned char con %u , %o , o %x , debe usar el modificador de longitud hh como parte de la especificación de conversión:

 printf( "%hhd ", *p ); 

El segundo problema es que la línea

 char *p = &y; 

es una violación de restricción – char * e int * no son tipos compatibles, y pueden tener diferentes tamaños y / o representaciones 2 . Por lo tanto, debe convertir explícitamente la fuente al tipo de destino:

 char *p = (char *) &y; 

La única excepción a esta regla se produce cuando uno de los operandos es void * ; entonces el yeso no es necesario.

Habiendo dicho todo eso, tomé su código y agregué una utilidad que volca la dirección y el contenido de los objetos en el progtwig. Así es como lucen y , p y j en mi 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 .... 

Estoy en un sistema x86, que es little-endian, por lo que almacena objetos de varios bytes comenzando con el byte menos significativo en la dirección más baja:

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

En un sistema little-endian, el byte direccionado es el byte menos significativo, que en este caso es 0xd2 ( 210 unsigned, -46 signed).

En pocas palabras, está imprimiendo la representación decimal firmada de ese byte único.

En cuanto a la pregunta más amplia, el tipo de la expresión *p es char y el tipo de la expresión *j es int ; el comstackdor simplemente va por el tipo de la expresión. El comstackdor realiza un seguimiento de todos los objetos, expresiones y tipos a medida que traduce su fuente al código máquina. Por lo tanto, cuando ve la expresión *j , sabe que se trata de un valor entero y genera código de máquina de manera apropiada. Cuando ve la expresión *p , sabe que se trata de un valor de char .


  1. Es cierto que casi todos los sistemas de escritorio modernos que conozco usan las mismas representaciones para todos los tipos de punteros, pero para más plataformas incrustadas o de propósito especial, eso puede no ser cierto.
  2. § 6.2.5, subcláusula 28.

(Tenga en cuenta que esta respuesta se refiere a la forma original de la pregunta, que preguntaba cómo sabía el progtwig cuántos bytes leer, etc. Lo mantengo sobre esa base, a pesar de que la alfombra se ha extraído de debajo).

Un puntero se refiere a una ubicación en la memoria que contiene un objeto particular y debe ser incrementada / decrementada / indexada con un tamaño de zancada particular, reflejando el tamaño del tipo de punta.

El valor observable del puntero en sí mismo (por ejemplo, a través de std::cout << ptr ) no necesita reflejar ninguna dirección física reconocible, ni ++ptr necesita incrementar dicho valor en 1, sizeof(*ptr) o cualquier otra cosa. Un puntero es solo un identificador para un objeto, con una representación de bits definida por la implementación. Esa representación no debe ni debe importar a los usuarios. Lo único por lo que los usuarios deberían usar el puntero es ... bueno, señalar cosas. Hablar de su dirección no es portable y solo es útil en la depuración.

De todos modos, simplemente, el comstackdor sabe cuántos bytes leer / escribir porque el puntero se escribe, y ese tipo tiene un sizeof definido, representación y mapeo de direcciones físicas. Entonces, basado en ese tipo, las operaciones en ptr se comstackrán en las instrucciones apropiadas para calcular la dirección real del hardware (que de nuevo, no necesita corresponderse con el valor observable de ptr ), lea el ptr correcto del número de 'bytes' de memoria, sumr / restar el número correcto de bytes para que apunte al siguiente objeto, etc.

Primero lea la advertencia que dice advertencia: inicialización desde un tipo de puntero incompatible [habilitado por defecto] char * p = & y;

lo que significa que debe hacer un encasillado explícito para evitar el comportamiento indefinido de acuerdo con el §7.21.6.1, subcláusula 9 estándar (señalado por @john Bode) como

 chat *p = (char*)&y; 

y

 int y =1234; 

aquí y es la local variable y se almacenará en la sección de stack de la RAM . En Linux, los enteros de la máquina se almacenan en la memoria de acuerdo con el formato little endian . Suponga que 4 bytes de memoria reservados para y es de 0x100 a 0x104

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

Como se señaló anteriormente, j y p apuntan a la misma dirección 0x100 pero cuando el comstackdor realiza *p ya que p está signed character pointer al signed character pointer de forma predeterminada, comprobará el sign bit y aquí el sign bit es 1 significa que una cosa es segura que la salida se va a imprimir es un número negativo

Si el sign bit es 1 es decir, número negativo y los números negativos se almacenan en la memoria como cumplido de 2, entonces

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

mientras imprime si está utilizando el especificador de formato %u que es para imprimir equivalentes unsigned , not verificará el sign bi , por último, se imprimirá cualquier dato que haya en 1 byte .

finalmente

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

En el enunciado anterior, al hacer desreferenciamiento j que es un signed pointer de forma predeterminada, es un puntero int por lo que se marcará el 31er bit para el signo , que es 0 significa que el resultado será positive no y ese es 1234.