Cuando se ejecuta un programa en C, los datos se almacenan en el montón o en la pila. Los valores se almacenan en direcciones RAM. Pero, ¿qué pasa con los indicadores de tipo (por ejemplo, int
o char
)? ¿También están almacenados?
Considere el siguiente código:
char a = 'A';
int x = 4;
Leí que A y 4 están almacenados en direcciones RAM aquí. Pero ¿qué pasa a
y x
? Más confuso, ¿cómo sabe la ejecución que a
es un char y x
es un int? Quiero decir, ¿se menciona el int
y char
en algún lugar de la RAM?
Digamos que un valor se almacena en algún lugar de la RAM como 10011001; si soy el programa que ejecuta el código, ¿cómo sabré si este 10011001 es un char
o un int
?
Lo que no entiendo es cómo sabe la computadora, cuando lee el valor de una variable de una dirección como 10001, si es un int
o char
. Imagina que hago clic en un programa llamado anyprog.exe
. Inmediatamente el código comienza a ejecutarse. ¿Este archivo ejecutable incluye información sobre si las variables almacenadas son del tipo int
o char
?
x
sea un carácter, pero es el código de impresión de caracteres que se ejecuta, porque eso es lo que seleccionó el compilador.Respuestas:
Para abordar la pregunta que ha publicado en varios comentarios (que creo que debería editar en su publicación):
Así que vamos a ponerle un código. Digamos que escribes:
Y supongamos que se almacena en la RAM:
La primera parte es la dirección, la segunda parte es el valor. Cuando su programa (que se ejecuta como código de máquina) se ejecuta, todo lo que ve
0x00010004
es el valor0x000000004
. No 'conoce' el tipo de estos datos, y no sabe cómo 'se supone' que se utilizarán.Entonces, ¿cómo determina su programa lo correcto? Considera este código:
Tenemos una lectura y una escritura aquí. Cuando su programa lee
x
de la memoria, encuentra0x00000004
allí. Y su programa sabe agregarlo0x00000005
. Y la razón por la cual su programa 'sabe' que esta es una operación válida, es porque el compilador asegura que la operación es válida a través de la seguridad de tipo. Su compilador ya ha verificado que puede agregar4
y5
juntos. Entonces, cuando se ejecuta su código binario (el exe), no tiene que hacer esa verificación. Simplemente ejecuta cada paso a ciegas, suponiendo que todo esté bien (las cosas malas suceden cuando en realidad están bien, no están bien).Otra forma de pensar es así. Te doy esta información:
Mismo formato que antes: dirección a la izquierda, valor a la derecha. ¿De qué tipo es el valor? En este punto, usted conoce tanta información sobre ese valor como su computadora cuando ejecuta código. Si le dijera que agregue 12743 a ese valor, podría hacerlo. No tienes idea de lo que las repercusiones de esa operación serán en todo el sistema, pero la adición de dos números es algo que es realmente bueno en, por lo que podría hacerlo. ¿Eso hace que el valor sea un
int
? No necesariamente: todo lo que ves son dos valores de 32 bits y el operador de suma.Tal vez parte de la confusión es recuperar los datos. Si tenemos:
¿Cómo sabe la computadora mostrar
a
en la consola? Bueno, hay muchos pasos para eso. El primero es ir aA
la ubicación de s en la memoria y leerlo:El valor hexadecimal para
a
en ASCII es 0x61, por lo que lo anterior podría ser algo que vería en la memoria. Entonces, nuestro código de máquina conoce el valor entero. ¿Cómo sabe convertir el valor entero en un carácter para mostrarlo? En pocas palabras, el compilador se aseguró de poner todos los pasos necesarios para hacer esa transición. Pero su computadora (o el programa / exe) no tiene idea de cuál es el tipo de datos. Ese valor de 32 bits podría ser cualquier cosa -int
,char
, la mitad de unadouble
, un puntero, que forma parte de una matriz, parte de unastring
, parte de una instrucción, etc.Aquí hay una breve interacción que su programa (exe) podría tener con la computadora / sistema operativo.
Programa: quiero comenzar. Necesito 20 MB de memoria.
Sistema operativo: encuentra 20 MB de memoria libre que no están en uso y los entrega
(La nota importante es que esto podría volver algún 20 MB libres de memoria, ni siquiera tienen que ser contiguos. En este punto, el programa ahora puede operar dentro de la memoria que tiene sin hablar con el sistema operativo)
Programa: voy a suponer que el primer punto en la memoria es una variable entera de 32 bits
x
.(El compilador se asegura de que los accesos a otras variables nunca toquen este punto en la memoria. No hay nada en el sistema que diga que el primer byte es variable
x
, o que la variablex
es un número entero. Una analogía: tienes una bolsa. Le dices a la gente que solo pondrás bolas de color amarillo en esta bolsa. Cuando alguien más tarde saque algo de la bolsa, sería sorprendente que sacara algo azul o un cubo, algo salió terriblemente mal. Lo mismo ocurre con las computadoras: su el programa ahora supone que el primer punto de memoria es la variable x y que es un número entero. Si alguna vez se escribe algo más sobre este byte de memoria o se supone que es otra cosa, ha sucedido algo horrible. El compilador asegura que este tipo de cosas no no suceda)Programa: ahora escribiré
2
en los primeros cuatro bytes donde supongo quex
está en.Programa: quiero agregar 5 a
x
.Lee el valor de X en un registro temporal
Agrega 5 al registro temporal
Almacena el valor del registro temporal de nuevo en el primer byte, que todavía se supone que es
x
.Programa: voy a asumir que el siguiente byte disponible es la variable char
y
.Programa: escribiré
a
en variabley
.Se usa una biblioteca para encontrar el valor de byte para
a
El byte se escribe en la dirección que el programa supone
y
.Programa: quiero mostrar el contenido de
y
Lee el valor en el segundo punto de memoria
Utiliza una biblioteca para convertir del byte a un carácter.
Utiliza bibliotecas de gráficos para alterar la pantalla de la consola (configuración de píxeles de negro a blanco, desplazamiento de una línea, etc.)
(Y continúa desde aquí)
Lo que probablemente te está colgando es: ¿qué sucede cuando el primer lugar en la memoria ya no está
x
? o el segundo ya no esy
? ¿Qué sucede cuando alguien leex
como unchar
oy
como un puntero? En resumen, suceden cosas malas. Algunas de estas cosas tienen un comportamiento bien definido, y algunas tienen un comportamiento indefinido. El comportamiento indefinido es exactamente eso: cualquier cosa puede suceder, de la nada a fallar el programa o el sistema operativo. Incluso el comportamiento bien definido puede ser malicioso. Si puedo cambiarx
a un puntero a mi programa y hacer que su programa lo use como puntero, entonces puedo hacer que su programa comience a ejecutar mi programa, que es exactamente lo que hacen los piratas informáticos. El compilador está ahí para ayudar a asegurarnos de que no lo usemosint x
comostring
y cosas de esa naturaleza. El código de la máquina en sí no conoce los tipos, y solo hará lo que las instrucciones le indiquen. También se descubre una gran cantidad de información en tiempo de ejecución: ¿qué bytes de memoria puede usar el programa? ¿x
Comienza en el primer byte o el 12?Pero puedes imaginar lo horrible que sería escribir programas como este (y puedes hacerlo en lenguaje ensamblador). Empiezas por 'declarar' tus variables: te dices a ti mismo que el byte 1 es
x
, el byte 2 esy
, y a medida que escribes cada línea de código, cargando y almacenando registros, tú (como humano) tienes que recordar cuál esx
y cuál uno esy
, porque el sistema no tiene idea. Y usted (como humano) tiene que recordar qué tiposx
y quéy
son, porque una vez más, el sistema no tiene idea.fuente
Otherwise how can console or text file outputs a character instead of int
Porque hay una secuencia diferente de instrucciones para generar el contenido de una ubicación de memoria como un entero o como caracteres alfanuméricos. El compilador conoce los tipos de variables, elige la secuencia de instrucciones apropiada en el momento de la compilación y la registra en el EXE.Creo que su pregunta principal parece ser: "Si el tipo se borra en tiempo de compilación y no se retiene en tiempo de ejecución, entonces, ¿cómo sabe la computadora si debe ejecutar un código que lo interpreta como un
int
código que lo interpreta como unchar
? "Y la respuesta es ... la computadora no. Sin embargo, el compilador sí lo sabe, y en primer lugar simplemente habrá puesto el código correcto en el binario. Si la variable se escribiera como
char
, entonces el compilador no pondría el código para tratarla comoint
en el programa, colocaría el código para tratarla como achar
.No hay razones para retener el tipo en tiempo de ejecución:
+
operador), por lo que no necesita el tipo de tiempo de ejecución por ese motivo. Sin embargo, de nuevo, el tipo de tiempo de ejecución es algo diferente al tipo estático de todos modos, por ejemplo, en Java, en teoría, podría borrar los tipos estáticos y aún mantener el tipo de tiempo de ejecución para el polimorfismo. Tenga en cuenta también que si descentraliza y especializa el código de búsqueda de tipos y lo coloca dentro del objeto (o clase), entonces tampoco necesariamente necesita el tipo de tiempo de ejecución, por ejemplo, C ++ vtables.La única razón para mantener el tipo en tiempo de ejecución en C sería para la depuración, sin embargo, la depuración generalmente se realiza con la fuente disponible, y luego simplemente puede buscar el tipo en el archivo fuente.
Type Erasure es bastante normal. No afecta la seguridad de los tipos: los tipos se verifican en el momento de la compilación, una vez que el compilador está convencido de que el programa es seguro, los tipos ya no son necesarios (por esa razón). No afecta el polimorfismo estático (también conocido como sobrecarga): una vez que se completa la resolución de sobrecarga y el compilador ha elegido la sobrecarga correcta, ya no necesita los tipos. Los tipos también pueden guiar la optimización, pero nuevamente, una vez que el optimizador ha elegido sus optimizaciones basadas en los tipos, ya no las necesita.
La retención de tipos en tiempo de ejecución solo es necesaria cuando desea hacer algo con los tipos en tiempo de ejecución.
Haskell es uno de los lenguajes de tipo estático más estrictos, rigurosos y de tipo seguro, y los compiladores de Haskell generalmente borran todos los tipos. (Creo que la excepción es la aprobación de diccionarios de métodos para clases de tipos).
fuente
char
en el binario compilado. No emite el código para unint
, no emite el código para unbyte
, no emite el código para un puntero, simplemente emite solo el código para achar
. No se toman decisiones de tiempo de ejecución en función del tipo. No necesitas el tipo. Es completamente y completamente irrelevante. Todas las decisiones relevantes ya se han tomado en tiempo de compilación.public class JoergsAwesomeNewType {};
¿Ver? ¡Acabo de inventar un nuevo tipo! ¡Necesitas comprar una nueva CPU!La computadora no "sabe" qué direcciones son qué, pero el conocimiento de qué es lo que se incluye en las instrucciones de su programa.
Cuando escribe un programa en C que escribe y lee una variable char, el compilador crea un código de ensamblaje que escribe ese dato en algún lugar como un char, y hay otro código en otro lugar que lee una dirección de memoria y lo interpreta como un char. Lo único que une estas dos operaciones es la ubicación de esa dirección de memoria.
Cuando llega el momento de leer, las instrucciones no dicen "ver qué tipo de datos hay", solo dice algo como "cargar esa memoria como flotante". Si la dirección de la que se va a leer se ha cambiado, o algo ha sobrescrito esa memoria con algo que no sea un flotante, la CPU cargará felizmente esa memoria como flotante, y como resultado pueden ocurrir todo tipo de cosas extrañas.
Mal tiempo de analogía: imagine un almacén de envío complicado, donde el almacén es memoria y las personas que recogen cosas es la CPU. Una parte del 'programa' del almacén coloca varios artículos en el estante. Otro programa va y toma artículos del almacén y los coloca en cajas. Cuando se retiran, no se comprueban, simplemente van a la papelera. Todo el almacén funciona con todo lo que funciona en sincronización, con los artículos correctos siempre en el lugar correcto en el momento correcto, de lo contrario, todo se bloquea, al igual que en un programa real.
fuente
No lo hace. Una vez que C se compila en el código de máquina, la máquina solo ve un montón de bits. La forma en que se interpretan esos bits depende de qué operaciones se realicen en ellos en lugar de algunos metadatos adicionales.
Los tipos que ingresas en tu código fuente son solo para el compilador. Toma el tipo que usted dice que se supone que son los datos y, lo mejor que puede, trata de asegurarse de que esos datos solo se usen de manera que tengan sentido. Una vez que el compilador ha hecho un trabajo tan bueno como pudo al verificar la lógica de su código fuente, lo convierte en código de máquina y descarta los datos de tipo, porque el código de máquina no tiene forma de representar eso (al menos en la mayoría de las máquinas) .
fuente
int a = 65
ychar b = 'A'
una vez que se compila el código.La mayoría de los procesadores proporcionan diferentes instrucciones para trabajar con datos de diferentes tipos, por lo que la información de tipo generalmente se "integra" en el código de máquina generado. No es necesario almacenar metadatos de tipo adicionales.
Algunos ejemplos concretos pueden ayudar. El siguiente código de máquina se generó usando gcc 4.1.2 en un sistema x86_64 que ejecuta SuSE Linux Enterprise Server (SLES) 10.
Asuma el siguiente código fuente:
Aquí está la carne del código de ensamblaje generado correspondiente a la fuente anterior (usando
gcc -S
), con comentarios añadidos por mí:Hay algunas cosas adicionales que siguen
ret
, pero no es relevante para la discusión.%eax
es un registro de datos de propósito general de 32 bits.%rsp
es un registro de 64 bits reservado para guardar el puntero de la pila , que contiene la dirección de lo último que se introdujo en la pila.%rbp
es un registro de 64 bits reservado para guardar el puntero de cuadro , que contiene la dirección del cuadro de pila actual . Un marco de pila se crea en la pila cuando ingresa una función, y reserva espacio para los argumentos de la función y las variables locales. Se accede a los argumentos y las variables mediante el uso de desplazamientos desde el puntero del marco. En este caso, la memoria para la variablex
es 12 bytes "debajo" de la dirección almacenada%rbp
.En el código anterior, copiamos el valor entero de
x
(1, almacenado en-12(%rbp)
) en el registro%eax
utilizando lamovl
instrucción, que se utiliza para copiar palabras de 32 bits de una ubicación a otra. Luego llamamosaddl
, que agrega el valor entero dey
(almacenado en-8(%rbp)
) al valor que ya está en%eax
. Luego guardamos el resultado en-4(%rbp)
, que esz
.Ahora cambiemos eso para que tratemos con
double
valores en lugar deint
valores:Correr
gcc -S
nuevamente nos da:Varias diferencias En lugar de
movl
yaddl
, usamosmovsd
yaddsd
(asignamos y agregamos flotantes de doble precisión). En lugar de almacenar valores provisionales%eax
, usamos%xmm0
.Esto es lo que quiero decir cuando digo que el tipo está "integrado" en el código de la máquina. El compilador simplemente genera el código de máquina correcto para manejar ese tipo particular.
fuente
Históricamente , C consideraba que la memoria consistía en varios grupos de ranuras numeradas de tipo
unsigned char
(también llamado "byte", aunque no siempre tiene que ser de 8 bits). Cualquier código que usara cualquier cosa almacenada en la memoria necesitaría saber en qué ranura o ranuras se almacenó la información, y saber qué se debe hacer con la información allí [por ejemplo, interprete los cuatro bytes que comienzan en la dirección 123: 456 como un código de 32 bits el valor de punto flotante "o" almacena los 16 bits inferiores de la cantidad calculada más recientemente en dos bytes comenzando en la dirección 345: 678]. La memoria en sí misma no sabría ni importaría lo que significaban los valores almacenados en las ranuras de memoria ". Si El código intentó escribir memoria usando un tipo y leerlo como otro, los patrones de bits almacenados por la escritura se interpretarían de acuerdo con las reglas del segundo tipo, con las consecuencias que pudieran resultar.Por ejemplo, si el código se almacenara
0x12345678
en un valor de 32 bitsunsigned int
y luego intentara leer dosunsigned int
valores consecutivos de 16 bits de su dirección y el de arriba, entonces, dependiendo de qué mitad delunsigned int
almacenamiento estaba donde, el código podría leer los valores 0x1234 y 0x5678, o 0x5678 y 0x1234.Sin embargo, el estándar C99 ya no requiere que la memoria se comporte como un grupo de ranuras numeradas que no saben nada sobre lo que representan sus patrones de bits . Se permite que un compilador se comporte como si las ranuras de memoria conocieran los tipos de datos que están almacenados en ellos, y solo permitirá que los datos que se escriben usando cualquier otro tipo que no
unsigned char
sean leídos usando el tipounsigned char
o el mismo tipo que fueron escritos con; Además, los compiladores pueden comportarse como si las ranuras de memoria tuvieran el poder y la inclinación de corromper arbitrariamente el comportamiento de cualquier programa que intente acceder a la memoria de una manera contraria a esas reglas.Dado:
algunas implementaciones pueden imprimir 0x1234, y otras pueden imprimir 0x5678, pero bajo el Estándar C99 sería legal que una implementación imprima "¡REGLAS FRINK!" o hacer cualquier otra cosa, bajo la teoría de que sería legal que las ubicaciones de memoria
a
contengan hardware que registre qué tipo se usó para escribirlas, y que dicho hardware responda a un intento de lectura no válido de cualquier manera, incluso causando "¡REGLAS MALDITAS!" para ser salida.Tenga en cuenta que no importa si realmente existe tal hardware: el hecho de que dicho hardware pueda existir legalmente hace que sea legal para los compiladores generar código que se comporta como si se estuviera ejecutando en dicho sistema. Si el compilador puede determinar que una ubicación de memoria en particular se escribirá como un tipo y se leerá como otro, puede pretender que se está ejecutando en un sistema cuyo hardware podría hacer tal determinación y podría responder con cualquier grado de capricho que el autor del compilador considere apropiado. .
El propósito de esta regla era permitir a los compiladores que sabían que un grupo de bytes que contenía un valor de algún tipo contenía un valor particular en algún momento, y que desde entonces no se había escrito ningún valor del mismo tipo, para inferir que ese grupo de bytes aún mantendría ese valor. Por ejemplo, un procesador había leído un grupo de bytes en un registro, y luego quería usar la misma información nuevamente mientras aún estaba en el registro, el compilador podía usar el contenido del registro sin tener que releer el valor de la memoria. Una optimización útil. Durante los primeros diez años de la regla, violarla generalmente significaría que si una variable se escribe con un tipo diferente al que se usa para leerla, la escritura puede o no afectar el valor leído. Tal comportamiento puede en algunos casos ser desastroso, pero en otros casos puede ser inofensivo,
Sin embargo, alrededor de 2009, los autores de algunos compiladores como CLANG han determinado que, dado que el Estándar permite a los compiladores hacer lo que quieran en los casos en que la memoria se escribe usando un tipo y se lee como otro, los compiladores deben inferir que los programas nunca recibirán información que pueda hacer que tal cosa ocurra. Dado que el Estándar dice que el compilador puede hacer lo que quiera cuando se recibe dicha entrada inválida, el código que solo tendría un efecto en los casos en que el Estándar no impone requisitos puede (y en opinión de algunos autores del compilador) debería omitirse como irrelevante Esto cambia el comportamiento de las violaciones de alias de ser como la memoria que, dada una solicitud de lectura, puede devolver arbitrariamente el último valor escrito usando el mismo tipo que una solicitud de lectura o cualquier valor más reciente escrito usando algún otro tipo,
fuente
int x,y,z;
la expresiónx*y > z
nunca haría otra cosa que devolver 1 o 0, o donde las violaciones de alias tendrían algún efecto aparte de permitir que el compilador devuelva arbitrariamente un valor antiguo o nuevo.unsigned char
valores que se utilizan para construir un tipo "provienen". Si un programa descomponga un puntero en ununsigned char[]
, muestre brevemente su contenido hexadecimal en la pantalla y luego borre el punterounsigned char[]
, y luego acepte algunos números hexadecimales del teclado, cópielos de nuevo en un puntero y luego desreferencia ese puntero , el comportamiento estaría bien definido en el caso en que el número que se escribió coincidiera con el número que se mostró.En C, no lo es. Otros lenguajes (por ejemplo, Lisp, Python) tienen tipos dinámicos, pero C está tipado estáticamente. Eso significa que su programa debe saber qué tipo de datos deben interpretar correctamente es un carácter, un entero, etc.
Por lo general, el compilador se encarga de esto por usted, y si hace algo mal, obtendrá un error (o advertencia) en tiempo de compilación.
fuente
10001
. Es su trabajo o el trabajo del compilador , dependiendo del caso, mantenerse al día con cosas como esas manualmente mientras escribe la máquina o el código de ensamblaje.Tienes que distinguir entre
compiletime
yruntime
por un ladocode
ydata
por otro.Desde la perspectiva de la máquina, no hay diferencia entre lo que llamas
code
oinstructions
lo que llamasdata
. Todo se reduce a números. Pero algunas secuencias, lo que llamaríamoscode
, hacen algo que encontramos útil, otras simplementecrash
la máquina.El trabajo que realiza la CPU es un simple bucle de 4 pasos:
instruction
)Esto se llama ciclo de instrucción .
a
yx
son variables, que son marcadores de posición para las direcciones, donde el programa podría encontrar el "contenido" de las variables. Entonces, cada vez quea
se usa la variable , existe efectivamente la dirección del contenido dea
usado.La ejecución no sabe nada. Por lo que se dijo en la introducción, la CPU solo obtiene datos e interpreta estos datos como instrucciones.
La función printf está diseñada para "saber", qué tipo de entrada está ingresando, es decir, su código resultante proporciona las instrucciones correctas sobre cómo manejar un segmento de memoria especial. Por supuesto, es posible generar una salida sin sentido: el uso de una dirección, donde no se almacena ninguna cadena junto con "% s"
printf()
dará como resultado una salida sin sentido detenida solo por una ubicación de memoria aleatoria, donde está un 0 (\0
).Lo mismo ocurre con el punto de entrada de un programa. Bajo el C64 fue posible poner sus programas en (casi) todas las direcciones conocidas. Los programas de ensamblaje se iniciaron con una instrucción llamada
sys
seguida de una dirección:sys 49152
era un lugar común para colocar su código de ensamblador. Pero nada le impedía cargar, por ejemplo, datos gráficos49152
, lo que resulta en un bloqueo de la máquina después de "comenzar" desde este punto. En este caso, el ciclo de instrucción comenzó con la lectura de "datos gráficos" y tratando de interpretarlo como "código" (que por supuesto no tenía sentido); los efectos fueron a veces asombrosos;)Como se dijo: el "contexto", es decir, las instrucciones anteriores y siguientes, ayuda a tratar los datos de la manera que queremos. Desde la perspectiva de la máquina, no hay diferencia en ninguna ubicación de memoria.
int
ychar
es solo vocabulario, que tiene sentido encompiletime
; duranteruntime
(en un nivel de ensamblaje), no haychar
oint
.La computadora no sabe nada. El programador lo hace. El código compilado genera el contexto , que es necesario para generar resultados significativos para los humanos.
Sí y no . La información, ya sea una
int
o unachar
se pierde. Pero, por otro lado, se conserva el contexto (las instrucciones que indican cómo manejar las ubicaciones de memoria, dónde se almacenan los datos); por lo que implícitamente sí, la "información" es implícitamente disponible.fuente
Mantengamos esta discusión solo en lenguaje C.
El programa al que se refiere está escrito en un lenguaje de alto nivel como C. La computadora solo entiende el lenguaje de máquina. Los lenguajes de nivel superior le dan al programador la capacidad de expresar la lógica de una manera más amigable para los humanos, que luego se traduce en código de máquina que el microprocesador puede decodificar y ejecutar. Ahora déjenos discutir el código que mencionó:
Tratemos de analizar cada parte:
Por lo tanto, los identificadores de tipo de datos int / char solo los utiliza el compilador y no el microprocesador durante la ejecución del programa. Por lo tanto, no se almacenan en la memoria.
fuente
Mi respuesta aquí es algo simplificada y se referirá solo a C.
No, la información de tipo no se almacena en el programa.
int
ochar
no son indicadores de tipo para la CPU; solo al compilador.El exe creado por el compilador tendrá instrucciones para manipular
int
s si la variable fue declarada como unint
. Del mismo modo, si la variable se declaró como achar
, el exe contendrá instrucciones para manipular achar
.Cía:
Este programa imprimirá su mensaje, ya que
char
yint
tienen los mismos valores en RAM.Ahora, si se está preguntando cómo se las
printf
arregla para generar65
unaint
yA
para unachar
, es porque debe especificar en la "cadena de formato" cómoprintf
debe tratar el valor .(Por ejemplo,
%c
significa tratar el valor como achar
, y%d
significa tratar el valor como un entero; sin embargo, el mismo valor en ambos sentidos).fuente
printf
. @OP:int a = 65; printf("%c", a)
saldrá'A'
. ¿Por qué? Porque al procesador no le importa. Para ello, todo lo que ve son bits. Su programa le dijo al procesador que almacenara 65 (casualmente el valor de'A'
en ASCII) ena
y luego mostrara un carácter, lo cual con gusto lo hace. ¿Por qué? Porque no le importaEn el nivel más bajo, en la CPU física real no hay ningún tipo (ignorando las unidades de coma flotante). Solo patrones de bits. Una computadora funciona manipulando patrones de bits, muy, muy rápido.
Eso es todo lo que la CPU hace, todo lo que puede hacer. No hay tal cosa como un int o un char.
Se ejecutará como:
La instrucción iadd activa el hardware que se comporta como si los registros 1 y 2 fueran enteros. Si en realidad no representan números enteros, todo tipo de cosas pueden salir mal más tarde. El mejor resultado suele ser estrellarse.
Está en el compilador elegir la instrucción correcta en función de los tipos proporcionados en la fuente, pero en el código de máquina real ejecutado por la CPU, no hay tipos, en ninguna parte.
editar: Tenga en cuenta que el código de máquina real no menciona de hecho 4, 5 o entero en ninguna parte. son solo dos patrones de bits, y una instrucción que toma dos patrones de bits, asume que son ints y los suma.
fuente
Respuesta corta, el tipo está codificado en las instrucciones de la CPU que genera el compilador.
Aunque la información sobre el tipo o el tamaño de la información no se almacena directamente, el compilador realiza un seguimiento de esta información al acceder, modificar y almacenar valores en estas variables.
No lo hace, pero cuando el compilador produce el código de la máquina, lo sabe. An
int
y achar
pueden ser de diferentes tamaños. En una arquitectura donde un carácter es del tamaño de un byte y un int es de 4 bytes, entonces la variablex
no está en la dirección 10001, sino también en 10002, 10003 y 10004. Cuando el código necesita cargar el valorx
en un registro de CPU, Utiliza las instrucciones para cargar 4 bytes. Al cargar un char, utiliza las instrucciones para cargar 1 byte.¿Cómo elegir cuál de las dos instrucciones? El compilador decide durante la compilación, no se realiza en tiempo de ejecución después de inspeccionar los valores en la memoria.
Tenga en cuenta también que los registros pueden ser de diferentes tamaños. En las CPU Intel x86, el EAX tiene 32 bits de ancho, la mitad es AX, que es 16, y AX se divide en AH y AL, ambos de 8 bits.
Entonces, si desea cargar un número entero (en CPU x86), use la instrucción MOV para enteros, para cargar un carácter use la instrucción MOV para caracteres. Ambos se llaman MOV, pero tienen diferentes códigos operativos. Efectivamente siendo dos instrucciones diferentes. El tipo de la variable está codificado en las instrucciones de uso.
Lo mismo sucede con otras operaciones. Existen muchas instrucciones para realizar la suma, según el tamaño de los operandos, e incluso si están firmados o no. Consulte https://en.wikipedia.org/wiki/ADD_(x86_instruction) que enumera las diferentes adiciones posibles.
Primero, un char sería 10011001, pero un int sería 00000000 00000000 00000000 10011001, porque son de diferentes tamaños (en una computadora con los mismos tamaños mencionados anteriormente). Pero vamos a considerar el caso para
signed char
vsunsigned char
.Lo que se almacena en una ubicación de memoria se puede interpretar de la forma que desee. Parte de las responsabilidades del compilador de C es garantizar que lo que se almacena y lee de una variable se realiza de manera coherente. Entonces, no es que el programa sepa lo que está almacenado en una ubicación de memoria, sino que acepta de antemano que siempre leerá y escribirá el mismo tipo de cosas allí. (sin contar cosas como los tipos de casting).
fuente
En lenguajes de verificación de tipos como C #, la compilación realiza la verificación de tipos. El código benji escribió:
Simplemente se negaría a compilar. De manera similar, si intentas multiplicar una cadena y un número entero (iba a decir agregar, pero el operador '+' está sobrecargado con concatenación de cadenas y podría funcionar).
El compilador simplemente se negaría a generar código de máquina a partir de este C #, sin importar cuánto se haya besado su cadena.
fuente
Las otras respuestas son correctas, ya que esencialmente todos los dispositivos de consumo que encontrará no almacenan información de tipo. Sin embargo, ha habido varios diseños de hardware en el pasado (y en la actualidad, en un contexto de investigación) que usan una arquitectura etiquetada : almacenan tanto los datos como el tipo (y posiblemente también otra información). Estos incluirían más prominentemente las máquinas Lisp .
Recuerdo vagamente haber escuchado sobre una arquitectura de hardware diseñada para programación orientada a objetos que tenía algo similar, pero ahora no puedo encontrarla.
fuente