(Fonte: sviluppatore incorporato.)
Prima di tutto, è necessario un linguaggio che compili le istruzioni della macchina, non alcune istruzioni intermedie (ad esempio, Python, Java). Se il compilatore si basa su bytecode Java, cosa esegue i bytecode?
Il requisito del codice macchina compilato elimina molti, molti linguaggi di alto livello.
In secondo luogo, il tuo linguaggio deve essere compilato per formulare le istruzioni di più piattaforme. OK, così Haskell può compilare x86 e ARM. Che ne dici delle altre decine di CPU là fuori?
Il linker di C mi dà una quantità pazzesca di potere. Posso inserire il codice in una posizione specifica. Dire che la mia CPU si avvia a 0x08000. Posso dire al linker di memorizzare il codice in quella posizione. Posso dire al linker di memorizzare determinati codici in determinati segmenti. Il caricatore inserirà questi segmenti in una posizione specifica. Questo è utile se ho il codice che voglio esaurire il flash rispetto al codice per esaurire la RAM. Il kernel di Linux mette il codice di avvio in un blocco di memoria che viene successivamente liberato, recuperando così la memoria dal codice che viene eseguito una volta sola, sempre.
Posso chiamare assembly da C e C dall'assemblaggio. L'avvio della CPU è tutto in assembly. Le cose di livello basso basso vengono eseguite in assembly perché l'ambiente C potrebbe non essere ancora inizializzato! (Qualcosa deve chiamare main ()). I costrutti di sincronizzazione CPU molto bassi (semafori, mutex, ecc.) Sono di solito in assembly perché richiedono specifiche istruzioni della CPU. Cache, MMU e così via sono solitamente configurati in piccoli pezzi di assemblaggio. (ARM è caricato con istruzioni specifiche per configurare la cache, MMU.)
C è uno strato sottile sul montaggio. C è più veloce dell'assemblaggio. Quindi C vince per cose di basso livello. C vince perché nessun altro ha inventato un concorrente.