Quando um programa C está em execução, os dados são armazenados na pilha ou na pilha. Os valores são armazenados nos endereços de RAM. Mas e os indicadores de tipo (por exemplo, int
ou char
)? Eles também são armazenados?
Considere o seguinte código:
char a = 'A';
int x = 4;
Eu li que A e 4 são armazenados nos endereços de RAM aqui. Mas a
e quanto x
? O mais confuso é: como a execução sabe que a
é um caractere e x
é um int? Quero dizer, é o int
e char
mencionado em algum lugar na RAM?
Digamos que um valor seja armazenado em algum lugar da RAM como 10011001; se eu sou o programa que executa o código, como vou saber se esse 10011001 é um char
ou um int
?
O que não entendo é como o computador sabe, quando lê o valor de uma variável de um endereço como 10001, se é um int
ou char
. Imagine clicar em um programa chamado anyprog.exe
. Imediatamente o código começa a executar. Este arquivo executável inclui informações sobre se as variáveis armazenadas são do tipo int
ou char
?
x
é um caractere, mas é o código de impressão de caracteres que é executado, porque foi o que o compilador selecionou.Respostas:
Para abordar a pergunta que você postou em vários comentários (que eu acho que você deve editar em sua postagem):
Então, vamos colocar algum código nele. Digamos que você escreva:
E vamos supor que ele seja armazenado na RAM:
A primeira parte é o endereço, a segunda parte é o valor. Quando seu programa (que é executado como código de máquina) é executado, tudo o que vê
0x00010004
é o valor0x000000004
. Ele não 'sabe' o tipo desses dados e não sabe como eles devem ser usados.Então, como o seu programa descobre a coisa certa a fazer? Considere este código:
Temos uma leitura e uma gravação aqui. Quando seu programa lê
x
da memória, ele fica0x00000004
lá. E seu programa sabe adicionar0x00000005
a ele. E a razão pela qual seu programa 'sabe' que essa é uma operação válida é porque o compilador garante que a operação seja válida por meio de segurança de tipo. Seu compilador já verificou que você pode adicionar4
e5
juntar. Portanto, quando seu código binário é executado (o exe), ele não precisa fazer essa verificação. Ele apenas executa cada etapa às cegas, assumindo que tudo está OK (coisas ruins acontecem quando na verdade, não estão OK).Outra maneira de pensar sobre isso é assim. Eu lhe dou esta informação:
Mesmo formato de antes - endereço à esquerda, valor à direita. Que tipo é o valor? Neste ponto, você conhece tanta informação sobre esse valor quanto o seu computador quando está executando o código. Se eu lhe dissesse para adicionar 12743 a esse valor, você poderia fazê-lo. Você não tem idéia de quais serão as repercussões dessa operação em todo o sistema, mas adicionar dois números é algo em que você é realmente bom, para que possa fazê-lo. Isso torna o valor um
int
? Não necessariamente - tudo o que você vê são dois valores de 32 bits e o operador de adição.Talvez um pouco da confusão esteja voltando aos dados. Se tiver-mos:
Como o computador sabe exibir
a
no console? Bem, existem muitos passos para isso. O primeiro é ir paraA
o local s na memória e lê-lo:O valor hexadecimal
a
em ASCII é 0x61, portanto, o acima pode ser algo que você veria na memória. Então agora nosso código de máquina conhece o valor inteiro. Como ele sabe transformar o valor inteiro em um caractere para exibi-lo? Simplificando, o compilador certificou-se de executar todas as etapas necessárias para fazer essa transição. Mas o seu próprio computador (ou o programa / exe) não tem idéia de qual é o tipo desses dados. Esse valor de 32 bits pode ser qualquer coisa -int
,char
metade de umdouble
, um ponteiro, parte de uma matriz, parte de astring
, parte de uma instrução, etc.Aqui está uma breve interação que seu programa (exe) pode ter com o computador / sistema operacional.
Programa: Eu quero iniciar. Eu preciso de 20 MB de memória.
Sistema operacional: encontra 20 MB de memória livre que não estão em uso e os entrega
(A nota importante é que este poderia voltar quaisquer 20 MB livres de memória, eles nem sequer tem que ser contíguos. Neste ponto, o programa pode agora operar dentro da memória que ele tem sem falar com o OS)
Programa: Vou assumir que o primeiro ponto na memória é uma variável inteira de 32 bits
x
.(O compilador garante que o acesso a outras variáveis nunca toque esse ponto na memória. Não há nada no sistema que diga que o primeiro byte é variável
x
ou que a variávelx
é um número inteiro. Uma analogia: você tem um saco. Você diz às pessoas que você só colocará bolas de cor amarela nessa sacola. Quando alguém mais tarde puxar algo da sacola, seria chocante que puxasse algo azul ou um cubo - algo deu terrivelmente errado. O mesmo vale para computadores: o seu Agora, o programa está assumindo que o primeiro ponto de memória é variável xe que é um número inteiro.Se algo mais for escrito sobre esse byte de memória ou se for considerado algo mais - algo horrível aconteceu. O compilador garante que esse tipo de coisa não seja acontecer)Programa: Agora vou escrever
2
nos quatro primeiros bytes em que suponho quex
esteja.Programa: quero adicionar 5 a
x
.Lê o valor de X em um registro temporário
Adiciona 5 ao registro temporário
Armazena o valor do registro temporário de volta no primeiro byte, que ainda é assumido
x
.Programa: Vou assumir que o próximo byte disponível é a variável char
y
.Programa: Escreverei
a
para a variávely
.Uma biblioteca é usada para encontrar o valor de byte para
a
O byte é gravado no endereço que o programa está assumindo
y
.Programa: quero exibir o conteúdo de
y
Lê o valor no segundo ponto de memória
Usa uma biblioteca para converter do byte para um caractere
Utiliza bibliotecas gráficas para alterar a tela do console (definindo pixels de preto para branco, rolando uma linha etc.)
(E continua a partir daqui)
O que você provavelmente está se incomodando é - o que acontece quando o primeiro lugar na memória não é mais
x
? ou o segundo não é maisy
? O que acontece quando alguém lêx
como umchar
ouy
como um ponteiro? Em suma, coisas ruins acontecem. Algumas dessas coisas têm um comportamento bem definido, e algumas têm um comportamento indefinido. O comportamento indefinido é exatamente isso - tudo pode acontecer, do nada, até travar o programa ou o sistema operacional. Mesmo um comportamento bem definido pode ser malicioso. Se eu posso mudarx
para um ponteiro para o meu programa e fazer com que seu programa o use como ponteiro, posso fazer com que seu programa comece a executar o meu programa - que é exatamente o que os hackers fazem. O compilador está lá para ajudar a garantir que não usamosint x
como umstring
e coisas dessa natureza. O código da máquina em si não está ciente dos tipos e fará apenas o que as instruções dizem. Também há uma grande quantidade de informações descobertas em tempo de execução: quais bytes de memória o programa pode usar? Será quex
começam no primeiro byte ou dia 12?Mas você pode imaginar o quão horrível seria realmente escrever programas como este (e você pode, na linguagem assembly). Você começa 'declarando' suas variáveis - diz a si mesmo que o byte 1 é
x
, o byte 2 éy
e, ao escrever cada linha de código, carregando e armazenando registros, você (como humano) precisa se lembrar qual éx
e qual uma éy
porque o sistema não tem ideia. E você (como humano) precisa se lembrar de que tiposx
e quaisy
são, porque mais uma vez - o sistema não tem idéia.fonte
Otherwise how can console or text file outputs a character instead of int
Porque existe uma sequência diferente de instruções para emitir o conteúdo de uma localização de memória como um número inteiro ou como caracteres alfanuméricos. O compilador conhece os tipos de variáveis e escolhe a sequência apropriada de instruções em tempo de compilação e a registra no EXE.Eu acho que sua principal pergunta parece ser: "Se o tipo é apagado no tempo de compilação e não retido no tempo de execução, como o computador sabe se deve executar o código que o interpreta como um
int
ou executar o código que o interpreta como umchar
? "E a resposta é ... o computador não. No entanto, o compilador não sabe, e ele vai ter simplesmente colocar o código correto no binário em primeiro lugar. Se a variável fosse digitada como
char
, então o compilador não colocaria o código para tratá-lo como umint
no programa, colocaria o código para tratá-lo como achar
.Não são motivos para reter o tipo em tempo de execução:
+
operador), portanto, não precisa do tipo de tempo de execução por esse motivo. No entanto, novamente, o tipo de tempo de execução é algo diferente do tipo estático de qualquer maneira, por exemplo, em Java, você poderia apagar teoricamente os tipos estáticos e ainda manter o tipo de tempo de execução para polimorfismo. Observe também que, se você descentraliza e especializa o código de pesquisa de tipo e o coloca dentro do objeto (ou classe), também não precisa necessariamente do tipo de tempo de execução, por exemplo, C ++ vtables.O único motivo para manter o tipo em tempo de execução em C seria a depuração; no entanto, a depuração geralmente é feita com a fonte disponível e, em seguida, você pode simplesmente procurar o tipo no arquivo de origem.
O tipo Erasure é bastante normal. Isso não afeta a segurança do tipo: os tipos são verificados no momento da compilação, quando o compilador estiver convencido de que o programa é seguro para o tipo, os tipos não serão mais necessários (por esse motivo). Ele não afeta o polimorfismo estático (também conhecido como sobrecarga): depois que a resolução da sobrecarga é concluída e o compilador seleciona a sobrecarga correta, ele não precisa mais dos tipos. Os tipos também podem orientar a otimização, mas, novamente, depois que o otimizador seleciona suas otimizações com base nos tipos, ele não precisa mais delas.
A retenção de tipos no tempo de execução é necessária apenas quando você deseja fazer algo com os tipos no tempo de execução.
Haskell é uma das linguagens de tipo estaticamente mais estritas, mais rigorosas e com segurança de tipo, e os compiladores Haskell geralmente apagam todos os tipos. (A exceção é a passagem de dicionários de métodos para classes de tipos, acredito.)
fonte
char
no binário compilado. Ele não emite o código de umint
, ele não emite o código para umbyte
, não saída o código para um ponteiro, ele simplesmente produz unicamente o código para umchar
. Não há decisões de tempo de execução sendo tomadas com base no tipo. Você não precisa do tipo. É completamente e totalmente irrelevante. Todas as decisões relevantes já foram tomadas em tempo de compilação.public class JoergsAwesomeNewType {};
Vejo? Acabei de inventar um novo tipo! Você precisa comprar uma nova CPU!O computador não "sabe" quais endereços são o quê, mas o conhecimento do que é o que está inserido nas instruções do seu programa.
Quando você escreve um programa C que grava e lê uma variável char, o compilador cria um código de montagem que grava esses dados em algum lugar como char, e há outro código em outro lugar que lê um endereço de memória e o interpreta como char. A única coisa que une essas duas operações é a localização desse endereço de memória.
Quando chega a hora de ler, as instruções não dizem "ver que tipo de dados existe", apenas dizem algo como "carregar essa memória como um flutuador". Se o endereço a ser lido tiver sido alterado ou algo sobrescrever essa memória por algo que não seja um float, a CPU simplesmente carregará essa memória como um float de qualquer maneira, e todos os tipos de coisas estranhas podem acontecer como resultado.
Tempo ruim de analogia: imagine um armazém de remessas complicado, onde o armazém é a memória e as pessoas que escolhem as coisas são a CPU. Uma parte do 'programa' do armazém coloca vários itens na prateleira. Outro programa vai e pega itens do armazém e os coloca em caixas. Quando são retirados, não são verificados, apenas vão para a lixeira. Todo o armazém funciona com tudo funcionando em sincronia, com os itens certos sempre no lugar certo, na hora certa; caso contrário, tudo falha, como em um programa real.
fonte
Não faz. Uma vez que C é compilado no código da máquina, a máquina vê apenas alguns bits. Como esses bits são interpretados depende de quais operações estão sendo executadas neles, em oposição a alguns metadados adicionais.
Os tipos digitados no seu código-fonte são apenas para o compilador. Ele usa o tipo que você diz que os dados devem ter e, na melhor das hipóteses, tenta garantir que esses dados sejam usados apenas de maneiras que façam sentido. Depois que o compilador faz o melhor trabalho possível na verificação da lógica do seu código-fonte, ele o converte em código de máquina e descarta os dados do tipo, porque o código de máquina não tem como representá-lo (pelo menos na maioria das máquinas) .
fonte
int a = 65
echar b = 'A'
depois que o código for compilado.A maioria dos processadores fornece instruções diferentes para trabalhar com dados de tipos diferentes; portanto, as informações de tipo geralmente são "inseridas" no código de máquina gerado. Não há necessidade de armazenar metadados de tipo adicionais.
Alguns exemplos concretos podem ajudar. O código de máquina abaixo foi gerado usando o gcc 4.1.2 em um sistema x86_64 executando o SuSE Linux Enterprise Server (SLES) 10.
Suponha o seguinte código-fonte:
Aqui está a descrição do código de montagem gerado correspondente à fonte acima (usando
gcc -S
), com comentários adicionados por mim:Há algumas coisas extras a seguir
ret
, mas não são relevantes para a discussão.%eax
é um registro de dados de uso geral de 32 bits.%rsp
é um registro de 64 bits reservado para salvar o ponteiro da pilha , que contém o endereço da última coisa colocada na pilha.%rbp
é um registro de 64 bits reservado para salvar o ponteiro de quadro , que contém o endereço do quadro de pilha atual . Um quadro de pilha é criado na pilha quando você insere uma função e reserva espaço para os argumentos e variáveis locais da função. Argumentos e variáveis são acessados usando deslocamentos do ponteiro do quadro. Nesse caso, a memória da variávelx
é de 12 bytes "abaixo" do endereço armazenado%rbp
.No código acima, copiamos o valor inteiro de
x
(1, armazenado em-12(%rbp)
) no registro%eax
usando amovl
instrução, que é usada para copiar palavras de 32 bits de um local para outro. Em seguidaaddl
, chamamos , que adiciona o valor inteiro dey
(armazenado em-8(%rbp)
) ao valor já existente%eax
. Em seguida, salvamos o resultado em-4(%rbp)
, o que éz
.Agora vamos mudar isso, então estamos lidando com
double
valores em vez deint
valores:Correr
gcc -S
novamente nos dá:Várias diferenças. Em vez de
movl
eaddl
, usamosmovsd
eaddsd
(atribuímos e adicionamos flutuadores de precisão dupla). Em vez de armazenar valores provisórios%eax
, usamos%xmm0
.É isso que quero dizer quando digo que o tipo é "incorporado" ao código da máquina. O compilador simplesmente gera o código de máquina certo para lidar com esse tipo específico.
fonte
Historicamente , C considerava a memória composta por vários grupos de slots numerados do tipo
unsigned char
(também chamado de "byte", embora nem sempre seja 8 bits). Qualquer código que usasse qualquer coisa armazenada na memória precisaria saber em qual slot ou slots as informações foram armazenadas e saber o que deve ser feito com as informações existentes [por exemplo "interpretar os quatro bytes começando no endereço 123: 456 como um arquivo de 32 bits. valor de ponto flutuante "ou" armazena os 16 bits mais baixos da quantidade calculada mais recentemente em dois bytes, começando no endereço 345: 678]. A própria memória não saberia nem se importaria com o que os valores armazenados nos slots de memória "significavam". Se o código tentasse escrever memória usando um tipo e lê-lo como outro, os padrões de bits armazenados pela gravação seriam interpretados de acordo com as regras do segundo tipo, com quaisquer consequências que possam resultar.Por exemplo, se o código fosse armazenado
0x12345678
em um de 32 bitsunsigned int
e, em seguida, tentasse ler dois valores consecutivos de 16 bitsunsigned int
do endereço e do acima, dependendo da metade da qualunsigned int
estava armazenada, o código pode ler os valores 0x1234 e 0x5678 ou 0x5678 e 0x1234.O padrão C99, no entanto, não exige mais que a memória se comporte como um monte de slots numerados que nada sabem sobre o que seus padrões de bits representam . É permitido que um compilador se comporte como se os slots de memória estivessem cientes dos tipos de dados armazenados neles, e só permitirá que dados gravados usando qualquer tipo que
unsigned char
não sejam lidos usando o tipounsigned char
ou o mesmo tipo como foram gravados. com; os compiladores também podem se comportar como se os slots de memória tivessem o poder e a inclinação de corromper arbitrariamente o comportamento de qualquer programa que tente acessar a memória de uma maneira contrária a essas regras.Dado:
algumas implementações podem imprimir 0x1234 e outras podem imprimir 0x5678, mas sob o C99 Standard, seria legal para uma implementação imprimir "REGRAS FRINK!" ou faça qualquer outra coisa, com a teoria de que seria legal para os locais de memória
a
que incluem incluir hardware que registre o tipo usado para gravá-los e que esse hardware responda a uma tentativa de leitura inválida de qualquer forma, inclusive causando "REGRAS FRINK!" para ser produzido.Observe que não importa se existe algum hardware - o fato de que esse hardware possa existir legalmente torna legal para os compiladores gerar código que se comporta como se estivesse sendo executado em um sistema assim. Se o compilador puder determinar que um determinado local de memória será gravado como um tipo e lido como outro, poderá fingir que está sendo executado em um sistema cujo hardware possa fazer essa determinação e responder com qualquer grau de capricho que o autor do compilador considerar adequado. .
O objetivo desta regra era permitir que os compiladores que sabiam que um grupo de bytes que detinha um valor de algum tipo mantinham um valor específico em algum ponto no tempo e que nenhum valor desse mesmo tipo havia sido gravado desde então, inferir que esse grupo de bytes ainda manteria esse valor. Por exemplo, um processador leu um grupo de bytes em um registro e, posteriormente, desejou usar as mesmas informações novamente enquanto ainda estavam no registro, o compilador poderia usar o conteúdo do registro sem precisar reler o valor da memória. Uma otimização útil. Nos primeiros dez anos da regra, violá-la geralmente significa que, se uma variável for escrita com um tipo diferente daquele usado para lê-la, a gravação poderá ou não afetar o valor lido. Esse comportamento pode em alguns casos ser desastroso, mas em outros casos pode ser inofensivo,
Por volta de 2009, no entanto, os autores de alguns compiladores como o CLANG determinaram que, como o Padrão permite que os compiladores façam o que quiserem nos casos em que a memória é escrita usando um tipo e lida como outro, os compiladores devem inferir que os programas nunca receberão informações que possam fazer com que isso ocorra. Como o Padrão diz que o compilador pode fazer o que quiser quando essa entrada inválida é recebida, código que só teria efeito nos casos em que o Padrão não impõe nenhum requisito pode (e na opinião de alguns autores do compilador) deve ser omitido como irrelevante. Isso altera o comportamento das violações de aliasing de serem como memória que, dada uma solicitação de leitura, pode arbitrariamente retornar o último valor gravado usando o mesmo tipo que uma solicitação de leitura ou qualquer valor mais recente gravado usando outro tipo,
fonte
int x,y,z;
a expressãox*y > z
, nada faria além de retornar 1 ou 0 ou onde as violações de aliasing teriam algum efeito. diferente de permitir que o compilador retorne arbitrariamente um valor antigo ou novo.unsigned char
vieram os valores usados para construir um tipo ". Se um programa decompor um ponteiro em umunsigned char[]
, mostre brevemente seu conteúdo hexadecimal na tela e, em seguida, apague o ponteiro, ounsigned char[]
, e depois aceite alguns números hexadecimais do teclado, copie-os novamente para um ponteiro e, em seguida, desreferencie esse ponteiro , o comportamento seria bem definido no caso em que o número digitado correspondesse ao número exibido.Em C, não é. Outros idiomas (por exemplo, Lisp, Python) têm tipos dinâmicos, mas C é de tipo estatístico. Isso significa que seu programa deve saber que tipo de dado os dados devem interpretar adequadamente como um caractere, um número inteiro etc.
Normalmente, o compilador cuida disso para você e, se você fizer algo errado, receberá um erro em tempo de compilação (ou aviso).
fonte
10001
. É o seu trabalho ou o do compilador , dependendo do caso, acompanhar coisas assim manualmente enquanto escreve o código da máquina ou montagem.Você precisa distinguir entre
compiletime
eruntime
por um ladocode
eedata
por outro lado.Do ponto de vista da máquina, não há diferença entre o que você chama
code
ouinstructions
o que chamadata
. Tudo se resume a números. Mas algumas seqüências - o que chamaríamoscode
- fazem algo que consideramos útil, outras simplesmentecrash
a máquina.O trabalho realizado pela CPU é um loop simples de 4 etapas:
instruction
)Isso é chamado de ciclo de instruções .
a
ex
são variáveis, que são espaços reservados para os endereços, onde o programa pode encontrar o "conteúdo" das variáveis. Portanto, sempre que a variávela
é usada, existe efetivamente o endereço do conteúdoa
usado.A execução não conhece nada. Pelo que foi dito na introdução, a CPU apenas busca dados e interpreta esses dados como instruções.
A função printf foi projetada para "saber", que tipo de entrada você está inserindo nela, ou seja, seu código resultante fornece as instruções corretas sobre como lidar com um segmento de memória especial. Obviamente, é possível gerar uma saída sem sentido: usando um endereço em que nenhuma string é armazenada junto com "% s"
printf()
resultará em uma saída sem sentido interrompida apenas por um local de memória aleatória, onde está um 0 (\0
).O mesmo vale para o ponto de entrada de um programa. Sob o C64, era possível colocar seus programas em (quase) todos os endereços conhecidos. Os programas de montagem foram iniciados com uma instrução chamada
sys
seguida por um endereço:sys 49152
era um local comum para colocar seu código de montagem. Mas nada o impediu de carregar, por exemplo, dados gráficos49152
, resultando em uma pane na máquina após "iniciar" a partir deste ponto. Nesse caso, o ciclo de instruções começou com a leitura de "dados gráficos" e a tentativa de interpretá-lo como "código" (o que, é claro, não fazia sentido); os efeitos foram surpreendentes;)Como dito: O "contexto" - isto é, as instruções anteriores e seguintes - ajuda a tratar os dados da maneira que queremos. Do ponto de vista da máquina, não há diferença em nenhum local da memória.
int
echar
é apenas vocabulário, o que faz sentidocompiletime
; duranteruntime
(no nível da montagem), não háchar
ouint
.O computador não sabe nada. O programador faz. O código compilado gera o contexto , necessário para gerar resultados significativos para humanos.
Sim e Não . A informação, se é um
int
ou achar
é perdida. Mas, por outro lado, o contexto (as instruções que informam, como lidar com os locais da memória, onde os dados são armazenados) é preservado; tão implicitamente sim, a "informação" está implicitamente disponível.fonte
Vamos manter essa discussão apenas na linguagem C.
O programa ao qual você está se referindo está escrito em uma linguagem de alto nível como C. O computador entende apenas a linguagem de máquina. Linguagens de nível superior dão ao programador a capacidade de expressar a lógica de uma maneira mais humana, que é então traduzida em código de máquina que o microprocessador pode decodificar e executar. Agora vamos discutir o código que você mencionou:
Vamos tentar analisar cada parte:
Portanto, os identificadores de tipo de dados int / char são usados apenas pelo compilador e não pelo microprocessador durante a execução do programa. Portanto, eles não são armazenados na memória.
fonte
Minha resposta aqui é um pouco simplificada e se referirá apenas a C.
Não, as informações de tipo não são armazenadas no programa.
int
ouchar
não são indicadores de tipo para a CPU; somente para o compilador.O exe criado pelo compilador terá instruções para manipular
int
s se a variável foi declarada como umint
. Da mesma forma, se a variável foi declarada como achar
, o exe conterá instruções para manipular achar
.Em C:
Este programa imprimirá sua mensagem, uma vez que os
char
eint
têm os mesmos valores na RAM.Agora, se você está se perguntando como
printf
gerencia a saída65
de umint
eA
para umchar
, é porque você precisa especificar na "cadeia de formato" comoprintf
deve tratar o valor .(Por exemplo,
%c
significa tratar o valor como achar
e%d
significa tratar o valor como um número inteiro; mesmo valor, de qualquer maneira.)fonte
printf
. @OP:int a = 65; printf("%c", a)
será exibido'A'
. Por quê? Porque o processador não se importa. Para isso, tudo o que vê são bits. Seu programa disse ao processador para armazenar 65 (coincidentemente o valor'A'
em ASCII)a
e depois gerar um caractere, o que é bom. Por quê? Porque não se importa.No nível mais baixo, na CPU física real, não há tipos (ignorando as unidades de ponto flutuante). Apenas padrões de bits. Um computador funciona manipulando padrões de bits, muito, muito rápido.
Isso é tudo o que a CPU faz, tudo o que pode fazer. Não existe int, nem char.
Será executado como:
A instrução iadd aciona o hardware que se comporta como se os registros 1 e 2 fossem números inteiros. Se eles não representam números inteiros, todos os tipos de coisas podem dar errado mais tarde. O melhor resultado é geralmente falhar.
Cabe ao compilador escolher a instrução correta com base nos tipos fornecidos na fonte, mas no código de máquina real executado pela CPU, não há tipos em nenhum lugar.
editar: Observe que o código de máquina real não menciona de fato 4, 5 ou número inteiro em qualquer lugar. são apenas dois padrões de bits e uma instrução que usa dois padrões de bits, assume que são ints e os soma.
fonte
Resposta curta, o tipo é codificado nas instruções da CPU que o compilador gera.
Embora as informações sobre o tipo ou tamanho das informações não sejam armazenadas diretamente, o compilador controla essas informações ao acessar, modificar e armazenar valores nessas variáveis.
Não, mas quando o compilador produz o código de máquina que conhece. Um
int
e umchar
podem ser de tamanhos diferentes. Em uma arquitetura em que um caractere é do tamanho de um byte e um int é de 4 bytes, a variávelx
não está no endereço 10001, mas também no 10002, 10003 e 10004. Quando o código precisa carregar o valorx
em um registro da CPU, Ele usa a instrução para carregar 4 bytes. Ao carregar um caracter, ele usa a instrução para carregar 1 byte.Como escolher qual das duas instruções? O compilador decide durante a compilação, isso não é feito em tempo de execução após a inspeção dos valores na memória.
Observe também que os registros podem ter tamanhos diferentes. Nas CPUs Intel x86, o EAX tem 32 bits de largura, metade dele é AX, que é 16, e o AX é dividido em AH e AL, ambos com 8 bits.
Portanto, se você deseja carregar um número inteiro (em CPUs x86), use a instrução MOV para números inteiros. Para carregar um caractere, use a instrução MOV para caracteres. Ambos são chamados de MOV, mas eles têm códigos op diferentes. Efetivamente sendo duas instruções diferentes. O tipo da variável é codificado na instrução a ser usada.
O mesmo acontece com outras operações. Existem muitas instruções para executar a adição, dependendo do tamanho dos operandos e mesmo se eles estão assinados ou não. Veja https://en.wikipedia.org/wiki/ADD_(x86_instruction), que lista diferentes adições possíveis.
Primeiro, um caractere seria 10011001, mas um int seria 00000000 00000000 00000000 10011001, porque são de tamanhos diferentes (em um computador com os mesmos tamanhos mencionados acima). Mas vamos considerar o caso de
signed char
vsunsigned char
.O que é armazenado em um local de memória pode ser interpretado da maneira que você desejar. Parte das responsabilidades do compilador C é garantir que o que é armazenado e lido de uma variável seja feito de maneira consistente. Portanto, não é que o programa saiba o que está armazenado em um local de memória, mas que concorda de antemão que sempre lerá e escreverá o mesmo tipo de coisas lá. (sem contar coisas como tipos de transmissão).
fonte
Em linguagens verificadas por tipo como C #, a verificação de tipo é feita pelo compilador. O código benji escreveu:
Simplesmente se recusaria a compilar. Da mesma forma, se você tentasse multiplicar uma string e um número inteiro (eu diria add, mas o operador '+' está sobrecarregado com concatenação de strings e pode funcionar).
O compilador simplesmente se recusaria a gerar código de máquina a partir desse C #, não importando o quanto sua string fosse aceita.
fonte
As outras respostas estão corretas, pois essencialmente todos os dispositivos de consumo que você encontrará não armazenam informações de tipo. No entanto, houve vários projetos de hardware no passado (e atualmente, em um contexto de pesquisa) que usam uma arquitetura marcada - eles armazenam os dados e o tipo (e possivelmente outras informações também). Isso incluiria com mais destaque as máquinas Lisp .
Lembro-me vagamente de ouvir sobre uma arquitetura de hardware projetada para programação orientada a objetos que tinha algo semelhante, mas não consigo encontrá-lo agora.
fonte