Oltre al codice macchina, non esiste un linguaggio di programmazione che viene eseguito direttamente sull'hardware, nel senso che non è possibile alimentare il testo di origine letterale. Tutte le implementazioni reali devono tradurre il programma sorgente nella lingua della "macchina".
Per alcune implementazioni, è tradotto staticamente. Di solito chiamiamo queste implementazioni "compilate". Per altri, viene tradotto in una forma intermedia, che viene quindi tradotta in modo dinamico durante l'esecuzione del programma. Generalmente chiamiamo queste interpretazioni "interpretate". Esiste un continuum di possibilità tra questi e persino molte CPU moderne eseguono traduzioni dinamiche come parte del suo core di esecuzione.
Anche quando il tuo programma è compilato staticamente molto prima dell'esecuzione, a meno che tu non stia scrivendo il firmware, è raro che il codice compilato venga eseguito direttamente sul bare metal senza nulla che lo supporti. Il sistema operativo fornisce una macchina virtuale per i programmi dello spazio utente, spesso fornendo funzionalità come l'illusione di avere una CPU tutta per te. L'illusione di uno spazio di memoria piatto che potrebbe essere più grande della RAM fisica collegata alla macchina è persino chiamata "memoria virtuale".
Inoltre, anche quando stai programmando in C, c'è una macchina virtuale C! È tradizionalmente indicato come "runtime C" o abbreviato CRT.
Poiché C viene tradotto principalmente in codice assembly / macchina con largo anticipo (su alcune piattaforme, potrebbe esserci anche un codice thread e che può essere considerato parte della macchina virtuale), la macchina virtuale di solito deve solo gestire l'avvio e spegnimento.
L'avvio comporta in genere l'impostazione dello stack e dell'heap; il sistema operativo fornisce raramente questi per te, ed è compito del linguaggio di programmazione fornirli al programmatore. Su alcune piattaforme potrebbe esserci qualche inizializzazione della gestione del segnale, impostazione del thread "principale" in un ambiente multi-thread, esecuzione di costruttori globali nella remota possibilità che il programma sia stato collegato al codice C ++, gestione di librerie collegate dinamicamente, o lì potrebbe essere necessaria l'elaborazione per configurare argc / argv ed envp. Infine, CRT trasferisce il controllo al principale.
Per quanto riguarda l'arresto, molti sistemi operativi possono uccidere un processo in modo impuro, quindi l'arresto non deve fare molto. La cosa principale è elaborare le richieste atexit () per il caso in cui il programma si chiude in modo pulito.