referencias - porque el cursor se mueve lento en autocad
Anular la referencia a este puntero me da-46, pero no estoy seguro de por qué (4)
Este es un programa que ejecuté:
#include <stdio.h>
int main(void)
{
int y = 1234;
char *p = &y;
int *j = &y;
printf("%d %d/n", *p, *j);
}
Estoy un poco confundido acerca de la salida. Lo que estoy viendo es:
-46 1234
Escribí este programa como un experimento y no estaba seguro de lo que iba a generar.
Esperaba posiblemente un byte de
y
.
¿Qué está pasando "detrás de escena" aquí?
¿Cómo me da
-46
desreferenciación?
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;
a
char *p = (char *)&y;
para no invalidar las respuestas a continuación.
Este programa no está causando ningún comportamiento de UB como se señala here .
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 de C 2011 , §7.21.6.1, subcláusula 9:
Si una especificación de conversión no es válida, el comportamiento es indefinido. 282) Si algún argumento no es el tipo correcto para la especificación de conversión correspondiente, el comportamiento es indefinido.
Sí, los objetos de tipo
char
se promueven a
int
cuando se pasan a funciones variadic;
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 un
char
con
%d
o argumento de
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
emitir explícitamente el origen al tipo de destino:
char *p = (char *) &y;
La
única
excepción a esta regla ocurre cuando uno de los operandos es
void *
;
entonces el elenco 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 programa.
Así es como se ven
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 que comienzan 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
sin signo,
-46
signo).
En pocas palabras, está imprimiendo la representación decimal firmada de ese solo byte.
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 compilador simplemente va por el tipo de expresión.
El compilador realiza un seguimiento de todos los objetos, expresiones y tipos a medida que traduce su fuente al código de máquina.
Entonces, cuando ve la expresión
*j
, sabe que se trata de un valor entero y genera el código de máquina de manera apropiada.
Cuando ve la expresión
*p
, sabe que se trata de un valor
char
.
- Es cierto que casi todos los sistemas de escritorio modernos que conozco usan las mismas representaciones para todos los tipos de punteros, pero para plataformas más extrañas incrustadas o de propósito especial, eso puede no ser cierto.
- § 6.2.5, inciso 28.
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 realizar una conversión de texto explícita para evitar un comportamiento indefinido de acuerdo con el estándar §7.21.6.1, subcláusula 9 (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
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 reservada 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 indicó anteriormente,
j
y
p
apuntan a la misma dirección
0x100
pero cuando el compilador realizará
*p
ya que
p
es un
signed character pointer
con
sign bit
por defecto, comprobará el
sign bit
y aquí el
sign bit
es
1
significa que una cosa es segura de que la salida se imprimirá Es
un número negativo
.
Si el
sign bit
es
1
es decir, el número negativo y los
números negativos se almacenan en la memoria como complemento 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 el equivalente
unsigned
,
not
verificará el
sign bi
t, finalmente, cualquier dato que haya en
1 byte
se imprimirá.
finalmente
printf("%d/n",*j);
En la declaración anterior al desreferenciar
j
que es un
signed pointer
por defecto y es un puntero
int
por lo que verificará
el bit 31
para el
signo
, que es
0
significa que la salida será
positive
no
positive
y eso es 1234.
Si tienes algo como
int x = 1234;
int *p = &x;
Si hace referencia al puntero
p
, leerá correctamente bytes enteros.
Porque lo declaraste como puntero a
int
.
Sabrá cuántos bytes leer por el operador
sizeof()
.
En general, 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 el byte que sea.
1234
en binario se representaría como
00000000 00000000 00000100 11010010
Ahora, si su máquina es little endian, almacenará los bytes invirtiéndolos
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
, solo leerá el primer byte y la salida será (
-46
en el caso de
signed char
y
210
en caso de
unsigned char
, de acuerdo con el estándar C, la firma de char simple es "implementación definida". ) como Byte read sería
11010010
(porque señalamos
signed char
(en este caso es
signed char
).
En su PC, los números negativos se representan como
Complemento de 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 declaramos que es 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 apuntado a ellos leerá tantos bytes de los que su tamaño sea,
char
es de 1 byte.
Como otros han señalado, debe convertir 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 escriba
char *p = (char *)&y
.
(Tenga en cuenta que esta respuesta se refiere a la forma original de la pregunta, que preguntaba cómo el programa sabía cuántos bytes leer, etc. Lo mantengo sobre esa base, a pesar de que la alfombra se ha sacado de debajo).
Un puntero se refiere a una ubicación en la memoria que contiene un objeto particular y debe incrementarse / decrementarse / indexarse con un tamaño de zancada particular, reflejando el tamaño del tipo puntiagudo.
El valor observable del puntero en sí (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 de un objeto, con una representación de bits definida por la implementación.
Esa representación no importa y no debería importar a los usuarios.
Lo único para lo que los usuarios deberían usar el puntero es ... bueno, señalar cosas.
Hablar de su dirección no es portátil y solo es útil en la depuración.
De todos modos, simplemente, el compilador sabe cuántos bytes leer / escribir porque se escribe el puntero, y ese tipo tiene un
sizeof
definido, representación y mapeo a direcciones físicas.
Entonces, según ese tipo, las operaciones en
ptr
se compilarán con las instrucciones apropiadas para calcular la dirección de hardware real (que, una vez más, no necesariamente corresponde al valor observable de
ptr
), lea el
ptr
correcto del número de ''bytes'' de memoria, suma / resta el número correcto de bytes para que apunte al siguiente objeto, etc.