Jaka jest różnica między kodem natywnym, kodem maszynowym i kodem asemblera?

106

Nie mam pojęcia co do kodu maszynowego i kodu natywnego w kontekście języków .NET.

Jaka jest różnica między nimi? Czy oni są tacy sami?

samaladeepak
źródło
3
Mam pytanie dotyczące tego pytania. Czy to pytanie spełnia wymagania StackOverflow? afaik to nie jest, ale jednocześnie tego rodzaju pytanie jest bardzo pomocne / pouczające. Zakładając, że tego typu pytania są niedozwolone, gdzie powinniśmy zadawać tego typu pytania, jeśli nie tutaj?
Yousuf Azad

Odpowiedzi:

150

Terminy są rzeczywiście nieco zagmatwane, ponieważ czasami są używane niekonsekwentnie.

Kod maszynowy: jest to najlepiej zdefiniowany. Jest to kod wykorzystujący instrukcje kodu bajtowego, które Twój procesor (fizyczny kawałek metalu wykonujący rzeczywistą pracę) rozumie i bezpośrednio wykonuje. Cały inny kod musi zostać przetłumaczony lub przekształcony w kod maszynowy, zanim maszyna będzie mogła go wykonać.

Kod macierzysty: ten termin jest czasami używany w miejscach, w których rozumie się kod maszynowy (patrz powyżej). Jednak czasami jest również używany w znaczeniu kodu niezarządzanego (patrz poniżej).

Kod niezarządzany i kod zarządzany: kod niezarządzany odnosi się do kodu napisanego w języku programowania, takim jak C lub C ++, który jest kompilowany bezpośrednio do kodu maszynowego . Kontrastuje to z kodem zarządzanym , który jest napisany w języku C #, VB.NET, Java lub podobnym i wykonywany w środowisku wirtualnym (takim jak .NET lub JavaVM), który rodzaj „symuluje” procesor w oprogramowaniu. Główna różnica polega na tym, że kod zarządzany „zarządza” zasobami (głównie alokacją pamięci) za Ciebie, wykorzystując wyrzucanie elementów bezużytecznych i utrzymując nieprzezroczyste odwołania do obiektów. Kod niezarządzanyjest rodzajem kodu, który wymaga ręcznego przydzielania i cofania alokacji pamięci, czasami powodując wycieki pamięci (gdy zapomnisz o cofnięciu alokacji), a czasami błędy segmentacji (gdy cofasz alokację zbyt wcześnie). Niezarządzane również zwykle oznacza, że ​​nie ma sprawdzania w czasie wykonywania typowych błędów, takich jak wyłuskiwanie wskaźnika null lub przepełnienie granic tablicy.

Ściśle mówiąc, większość języków z typami dynamicznymi - na przykład Perl, Python, PHP i Ruby - jest również kodem zarządzanym . Jednak nie są one powszechnie opisywane jako takie, co pokazuje, że kod zarządzany jest w rzeczywistości terminem marketingowym dla naprawdę dużych, poważnych, komercyjnych środowisk programistycznych (.NET i Java).

Kod asemblerowy: ten termin ogólnie odnosi się do rodzaju kodu źródłowego, który ludzie piszą, kiedy naprawdę chcą pisać kod bajtowy. Assembler to program, który zamienia ten kod źródłowy do kodu bajtowego rzeczywistym. To nie jest kompilator, ponieważ transformacja jest 1 do 1. Jednak termin jest niejednoznaczny, jeśli chodzi o rodzaj używanego kodu bajtowego: może być zarządzany lub niezarządzany. Jeśli nie jest zarządzany, wynikowy kod bajtowy jest kodem maszynowym . Jeśli jest zarządzany, skutkuje to kodem bajtowym używanym za kulisami przez środowisko wirtualne, takie jak .NET. Kod zarządzany (np. C #, Java) jest kompilowany do tego specjalnego języka kodu bajtowego, który w przypadku .NET nazywa się Common Intermediate Language (CIL), aw Javie nazywa się kodem bajtowym Java. Zwykły programista nie ma zwykle potrzeby dostępu do tego kodu lub bezpośredniego pisania w tym języku, ale kiedy ludzie to robią, często nazywają go kodem asemblera, ponieważ używają asemblera do przekształcenia go w kod bajtowy.

Timwi
źródło
C ++ może kompilować do kodu maszynowego, ale bardzo często jest kompilowany do innych formatów, takich jak exe, które będą działać z systemem operacyjnym.
Gordon Gustafson
Istnieją języki, które obsługują wyrzucanie elementów bezużytecznych i nieprzezroczyste odwołania, które zwykle kompilują się do kodu maszynowego. Robią to najpoważniejsze implementacje Common Lisp. To, co powiesz, może dotyczyć języków obsługiwanych przez firmę Microsoft, ale jest więcej języków kompilowanych niż obsługiwane przez program Visual Studio.
David Thornley
3
@CrazyJugglerDrummer: Kod zawarty w plikach EXE generowanych przez kompilatory C ++ jest nadal kodem maszynowym. @David Thornley: Wspomniałem o znacznie większej liczbie języków niż tylko te, ale nie chciałem komplikować sprawy, wspominając o każdej niejasnej osobliwości.
Timwi
Niektóre kompilatory, wiele z nich, faktycznie kompilują z C / C ++ lub innych języków do języka asemblera, a następnie wywołują asembler, a asembler przekształca go w pliki obiektowe, które są w większości kodem maszynowym, ale wymagają kilku dotknięć, zanim będą mogły przejść do pamięci procesora konsolidator łączy to wszystko z wersją programu w kodzie maszynowym. Chodzi o to, że C / C ++ itp. Często nie kompiluje się bezpośrednio do kodu maszynowego, ponieważ niewidoczny dla użytkownika wykonuje dwa lub trzy kroki po drodze. Wyjątkiem jest na przykład TCC, który przechodzi bezpośrednio do kodu maszynowego.
old_timer
To przypomina szukanie dziur w czubku, ale nie wszyscy asemblerzy tłumaczą 1-1 na opkody. W rzeczywistości wiele nowoczesnych asemblerów obsługuje konstrukcje abstrakcji, takie jak klasy. Przykład: TASM, asembler firmy Borland. en.wikipedia.org/wiki/TASM
Prime
45

To, co widzisz, gdy używasz Debug + Windows + Demontaż podczas debugowania programu C #, jest dobrym przewodnikiem dla tych warunków. Oto wersja z adnotacjami, gdy kompiluję program „Hello world” napisany w C # w konfiguracji wydania z włączoną optymalizacją JIT:

        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

Kliknij prawym przyciskiem myszy okno i zaznacz opcję „Pokaż bajty kodu”, aby uzyskać podobny widok.

Kolumna po lewej stronie to adres kodu maszynowego. Debugger fałszuje jego wartość, kod faktycznie znajduje się gdzie indziej. Ale może to być gdziekolwiek, w zależności od lokalizacji wybranej przez kompilator JIT, więc debugger zaczyna numerować adresy od 0 na początku metody.

Druga kolumna to kod maszynowy . Rzeczywiste 1 i 0, które wykonuje procesor. Kod maszynowy, jak tutaj, jest zwykle wyświetlany w postaci szesnastkowej. Ilustracją może być to, że 0x8B wybiera instrukcję MOV, dodatkowe bajty są tam, aby powiedzieć procesorowi dokładnie, co ma zostać przeniesione. Zwróć także uwagę na dwa rodzaje instrukcji CALL, 0xE8 to wywołanie bezpośrednie, 0xFF to instrukcja wywołania pośredniego.

Trzecia kolumna to kod zespołu . Asembler to prosty język, zaprojektowany, aby ułatwić pisanie kodu maszynowego. Porównuje się do kompilacji języka C # do IL. Kompilator używany do tłumaczenia kodu asemblera nazywany jest „assemblerem”. Prawdopodobnie masz asembler firmy Microsoft na swoim komputerze, jego nazwa wykonywalna to ml.exe, ml64.exe dla wersji 64-bitowej. W użyciu są dwie popularne wersje języków asemblera. Ten, który widzisz, to ten, którego używają Intel i AMD. W świecie open source asemblacja w notacji AT&T jest powszechna. Składnia języka jest silnie zależna od rodzaju procesora, dla którego został napisany, język asemblera dla PowerPC jest zupełnie inny.

OK, to dotyczy dwóch terminów z twojego pytania. „Kod natywny” jest terminem rozmytym i nierzadko jest używany do opisywania kodu w niezarządzanym języku. Być może pouczające jest sprawdzenie, jaki rodzaj kodu maszynowego jest generowany przez kompilator C. To jest wersja „hello world” w języku 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   

Nie dodałem adnotacji, głównie dlatego, że jest tak podobny do kodu maszynowego generowanego przez program C #. Wywołanie funkcji printf () różni się znacznie od wywołania Console.WriteLine (), ale wszystko inne jest mniej więcej takie samo. Należy również zauważyć, że debugger generuje teraz prawdziwy adres kodu maszynowego i że jest nieco mądrzejszy w zakresie symboli. Efekt uboczny generowania informacji debugowania po wygenerowaniu kodu maszynowego, tak jak często robią to niezarządzane kompilatory. Powinienem również wspomnieć, że wyłączyłem kilka opcji optymalizacji kodu maszynowego, aby kod maszynowy wyglądał podobnie. Kompilatory C / C ++ mają dużo więcej czasu na optymalizację kodu, wynik jest często trudny do zinterpretowania. I bardzo trudne do debugowania.

Kluczową kwestią jest to, że istnieje bardzo niewiele różnic między kodem maszynowym wygenerowanym z języka zarządzanego przez kompilator JIT a kodem maszynowym wygenerowanym przez kompilator kodu natywnego. To jest główny powód, dla którego język C # może konkurować z kompilatorem kodu natywnego. Jedyną prawdziwą różnicą między nimi są wywołania funkcji wsparcia. Wiele z nich jest zaimplementowanych w środowisku CLR. A to przede wszystkim kręci się wokół garbage collectora.

Hans Passant
źródło
6

Kod natywny i kod maszynowy to to samo - rzeczywiste bajty wykonywane przez procesor.

Kod asemblera ma dwa znaczenia: jedno to kod maszynowy przetłumaczony na bardziej czytelną dla człowieka formę (z bajtami instrukcji przetłumaczonymi na krótkie, podobne do słów mnemoniki, takie jak „JMP” (które „skaczą” w inne miejsce w kodzie). to kod bajtowy IL (bajty instrukcji generowane przez kompilatory, takie jak C # lub VB, które ostatecznie zostaną przetłumaczone na kod maszynowy, ale jeszcze nie są), który znajduje się w bibliotece DLL lub EXE.

cHao
źródło
2

W .NET zestawy zawierają kod MS Intermediate Language (MSIL, czasami CIL).
To jest jak kod maszynowy „wysokiego poziomu”.

Po załadowaniu MSIL jest kompilowany przez kompilator JIT do kodu natywnego (kod maszynowy Intel x86 lub x64).

Henk Holterman
źródło