Poza kodem maszynowym nie istnieje język programowania, który działa bezpośrednio na sprzęcie, w tym sensie, że nie można go podać dosłownego tekstu źródłowego. Wszystkie rzeczywiste implementacje muszą tłumaczyć program źródłowy na język „maszyny”.
W przypadku niektórych implementacji jest on tłumaczony statycznie. Zwykle nazywamy te implementacje „skompilowanymi”. W innych przypadkach jest on tłumaczony na formę pośrednią, która jest następnie dynamicznie tłumaczona podczas uruchamiania programu. Zwykle nazywamy te implementacje „interpretowanymi”. Istnieje ciąg możliwości między nimi, a nawet wiele współczesnych procesorów wykonuje dynamiczne tłumaczenie jako część rdzenia wykonawczego.
Nawet jeśli Twój program jest kompilowany statycznie na długo przed uruchomieniem, chyba że piszesz oprogramowanie układowe, rzadko kiedy skompilowany kod działa bezpośrednio na goły sposób i nic go nie obsługuje. System operacyjny zapewnia maszynę wirtualną dla programów przestrzeni użytkownika, często zapewniając takie funkcje, jak złudzenie, że masz cały procesor dla siebie. Złudzenie płaskiej przestrzeni pamięci, która może być większa niż fizyczna pamięć RAM podłączona do maszyny, nazywana jest nawet „pamięcią wirtualną”.
Co więcej, nawet podczas programowania w C, istnieje maszyna wirtualna w języku C! Jest to tradycyjnie nazywane „środowiskiem uruchomieniowym C” lub w skrócie CRT.
Ponieważ C jest w większości tłumaczone bezpośrednio na kod asemblera / kodu maszynowego z dużym wyprzedzeniem (na niektórych platformach może być także kod wątkowy , który można uznać za część maszyny wirtualnej), maszyna wirtualna zazwyczaj musi tylko obsługiwać uruchamianie i zamknąć.
Uruchomienie zwykle obejmuje ustawienie stosu i sterty; system operacyjny rzadko je udostępnia, a zadaniem programisty jest dostarczenie ich programistom. Na niektórych platformach może wystąpić pewna inicjalizacja obsługi sygnałów, konfiguracja wątku „głównego” w środowisku wielowątkowym, uruchamianie globalnych konstruktorów, gdy istnieje szansa, że program został powiązany z kodem C ++, obsługa dynamicznie połączonych bibliotek lub tam może być konieczne przetwarzanie w celu skonfigurowania argc / argv i envp. Na koniec CRT przenosi kontrolę na główny.
Jeśli chodzi o zamykanie, wiele systemów operacyjnych może zabić proces w nieczysty sposób, więc zamykanie nie musi zbyt wiele robić. Najważniejsze jest przetworzenie wywołań atexit () w przypadku, gdy program kończy działanie w sposób czysty.