Estou confuso sobre o código de máquina e o código nativo no contexto das linguagens .NET.
Qual a diferença entre eles? Eles são os mesmos?
Estou confuso sobre o código de máquina e o código nativo no contexto das linguagens .NET.
Qual a diferença entre eles? Eles são os mesmos?
Respostas:
Os termos são realmente um pouco confusos, porque às vezes são usados de forma inconsistente.
Código de máquina: Este é o mais bem definido. É um código que usa as instruções de código de byte que seu processador (a peça de metal física que faz o trabalho real) entende e executa diretamente. Todos os outros códigos devem ser traduzidos ou transformados em código de máquina antes que sua máquina possa executá-lo.
Código nativo: este termo às vezes é usado em lugares onde se refere o código de máquina (veja acima). No entanto, às vezes também é usado para significar código não gerenciado (veja abaixo).
Código não gerenciado e código gerenciado : o código não gerenciado se refere ao código escrito em uma linguagem de programação como C ou C ++, que é compilado diretamente no código de máquina . Ele contrasta com o código gerenciado , que é escrito em C #, VB.NET, Java ou similar e executado em um ambiente virtual (como .NET ou JavaVM) que meio que “simula” um processador em software. A principal diferença é que o código gerenciado “gerencia” os recursos (principalmente a alocação de memória) para você, empregando a coleta de lixo e mantendo as referências a objetos opacos. Código não gerenciadoé o tipo de código que requer que você aloque e desaloque manualmente a memória, às vezes causando vazamentos de memória (quando você se esquece de desalocar) e às vezes falhas de segmentação (quando você desaloca muito cedo). Não gerenciado também geralmente implica que não há verificações em tempo de execução para erros comuns, como desreferenciamento de ponteiro nulo ou estouro de limites de array.
Estritamente falando, a maioria das linguagens tipadas dinamicamente - como Perl, Python, PHP e Ruby - também são códigos gerenciados . No entanto, eles não são comumente descritos como tal, o que mostra que o código gerenciado é, na verdade, um termo de marketing para ambientes de programação comercial realmente grandes e sérios (.NET e Java).
Código assembly: este termo geralmente se refere ao tipo de código-fonte que as pessoas escrevem quando na verdade desejam escrever código de bytes. Um assembler é um programa que transforma esse código-fonte em um código de bytes real. Não é um compilador porque a transformação é de 1 para 1. No entanto, o termo é ambíguo quanto ao tipo de código de byte usado: ele pode ser gerenciado ou não gerenciado. Se não for gerenciado, o código de byte resultante é o código de máquina . Se for gerenciado, resulta no código de bytes usado nos bastidores por um ambiente virtual como o .NET. O código gerenciado (por exemplo, C #, Java) é compilado nesta linguagem de código de byte especial, que no caso do .NET é chamada Common Intermediate Language (CIL) e em Java é chamada de byte-code Java. Geralmente, há pouca necessidade para o programador comum acessar esse código ou escrever nessa linguagem diretamente, mas quando as pessoas o fazem, geralmente se referem a ele como código assembly porque usam um assembler para transformá-lo em código de bytes.
O que você vê ao usar Debug + Windows + Disassembly ao depurar um programa C # é um bom guia para esses termos. Aqui está uma versão anotada dele quando compilo um programa 'hello world' escrito em C # na configuração da versão com a otimização JIT habilitada:
static void Main(string[] args) {
Console.WriteLine("Hello world");
00000000 55 push ebp ; save stack frame pointer
00000001 8B EC mov ebp,esp ; setup current frame
00000003 E8 30 BE 03 6F call 6F03BE38 ; Console.Out property getter
00000008 8B C8 mov ecx,eax ; setup "this"
0000000a 8B 15 88 20 BD 02 mov edx,dword ptr ds:[02BD2088h] ; arg = "Hello world"
00000010 8B 01 mov eax,dword ptr [ecx] ; TextWriter reference
00000012 FF 90 D8 00 00 00 call dword ptr [eax+000000D8h] ; TextWriter.WriteLine()
00000018 5D pop ebp ; restore stack frame pointer
}
00000019 C3 ret ; done, return
Clique com o botão direito na janela e marque "Mostrar bytes de código" para obter uma exibição semelhante.
A coluna à esquerda é o endereço do código da máquina. Seu valor é falsificado pelo depurador, o código está realmente localizado em outro lugar. Mas isso pode ser em qualquer lugar, dependendo do local selecionado pelo compilador JIT, então o depurador apenas começa a numerar os endereços de 0 no início do método.
A segunda coluna é o código da máquina . Os reais 1s e 0s que a CPU executa. O código de máquina, como aqui, é comumente exibido em hexadecimal. Talvez seja ilustrativo que 0x8B seleciona a instrução MOV, os bytes adicionais estão lá para dizer à CPU exatamente o que precisa ser movido. Observe também os dois sabores da instrução CALL, 0xE8 é a chamada direta, 0xFF é a instrução de chamada indireta.
A terceira coluna é o código de montagem . Assembly é uma linguagem simples, projetada para tornar mais fácil escrever código de máquina. Ele se compara ao C # sendo compilado para IL. O compilador usado para traduzir o código assembly é chamado de "assembler". Você provavelmente tem o Microsoft assembler em sua máquina, seu nome executável é ml.exe, ml64.exe para a versão de 64 bits. Existem duas versões comuns de linguagens assembly em uso. O que você vê é aquele que a Intel e a AMD usam. No mundo do código aberto, a montagem na notação AT&T é comum. A sintaxe da linguagem é fortemente dependente do tipo de CPU para a qual foi escrito, a linguagem assembly para um PowerPC é muito diferente.
Ok, isso aborda dois dos termos em sua pergunta. "Código nativo" é um termo difuso, não é incomum para descrever o código em uma linguagem não gerenciada. Talvez seja instrutivo ver que tipo de código de máquina é gerado por um compilador C. Esta é a versão 'hello world' em C:
int _tmain(int argc, _TCHAR* argv[])
{
00401010 55 push ebp
00401011 8B EC mov ebp,esp
printf("Hello world");
00401013 68 6C 6C 45 00 push offset ___xt_z+128h (456C6Ch)
00401018 E8 13 00 00 00 call printf (401030h)
0040101D 83 C4 04 add esp,4
return 0;
00401020 33 C0 xor eax,eax
}
00401022 5D pop ebp
00401023 C3 ret
Eu não anotei, principalmente porque é muito semelhante ao código de máquina gerado pelo programa C #. A chamada da função printf () é bem diferente da chamada Console.WriteLine (), mas todo o resto é quase o mesmo. Observe também que o depurador agora está gerando o endereço do código de máquina real e que é um pouco mais inteligente sobre os símbolos. Um efeito colateral da geração de informações de depuração após gerar código de máquina, como costuma acontecer com os compiladores não gerenciados. Devo também mencionar que desativei algumas opções de otimização de código de máquina para fazer o código de máquina parecer semelhante. Os compiladores C / C ++ têm muito mais tempo disponível para otimizar o código; o resultado costuma ser difícil de interpretar. E muito difícil de depurar.
O ponto principal aqui é que há muito poucas diferenças entre o código de máquina gerado a partir de uma linguagem gerenciada pelo compilador JIT e o código de máquina gerado por um compilador de código nativo. Essa é a principal razão pela qual a linguagem C # pode ser competitiva com um compilador de código nativo. A única diferença real entre eles são as chamadas de função de suporte. Muitos dos quais são implementados no CLR. E isso gira principalmente em torno do coletor de lixo.
Código nativo e código de máquina são a mesma coisa - os bytes reais que a CPU executa.
O código assembly tem dois significados: um é o código de máquina traduzido em uma forma mais legível (com os bytes para as instruções traduzidos em mnemônicos semelhantes a palavras como "JMP" (que "salta" para outro ponto no código). O outro é o bytecode IL (bytes de instrução que compiladores como C # ou VB geram, que acabarão sendo traduzidos em código de máquina eventualmente, mas ainda não são) que reside em uma DLL ou EXE.
Em .NET, os assemblies contêm código MS Intermediate Language (MSIL, às vezes CIL).
É como um código de máquina de 'alto nível'.
Quando carregado, o MSIL é compilado pelo compilador JIT em código nativo (código de máquina Intel x86 ou x64).