In primo luogo, noterò che anche se cito solo "C" qui, lo stesso vale anche per C ++.
Il commento che menzionava Godel era in parte (ma solo in parte) in discussione.
Quando si arriva a questo, un comportamento indefinito negli standard C sta in gran parte solo evidenziando il confine tra ciò che lo standard tenta di definire e ciò che non lo fa.
I teoremi di Godel (ce ne sono due) sostengono sostanzialmente che è impossibile definire un sistema matematico che può essere provato (secondo le sue stesse regole) come completo e coerente. Puoi fare le tue regole in modo che possano essere complete (il caso che ha affrontato erano le regole "normali" per i numeri naturali), oppure puoi rendere possibile dimostrarne la coerenza, ma non puoi avere entrambe.
Nel caso di qualcosa come C, ciò non si applica direttamente - per la maggior parte, la "dimostrabilità" della completezza o coerenza del sistema non è una priorità per la maggior parte dei progettisti del linguaggio. Allo stesso tempo, sì, probabilmente sono stati influenzati (almeno in una certa misura) sapendo che è dimostrabilmente impossibile definire un sistema "perfetto" - uno che è dimostratamente completo e coerente. Sapere che una cosa del genere è impossibile potrebbe aver reso un po 'più facile fare un passo indietro, respirare un po' e decidere i limiti di ciò che avrebbero cercato di definire.
A rischio di essere (ancora una volta) accusato di arroganza, definirei lo standard C come governato (in parte) da due idee di base:
- Il linguaggio dovrebbe supportare la maggior varietà possibile di hardware (idealmente, tutto l'hardware "sano" fino a un limite inferiore ragionevole).
- La lingua dovrebbe supportare la scrittura della più ampia varietà di software possibile per l'ambiente dato.
Il primo significa che se qualcuno definisce una nuova CPU, dovrebbe essere possibile fornire un'implementazione buona, solida e utilizzabile di C, a condizione che il design cada almeno ragionevolmente vicino ad alcune semplici linee guida - fondamentalmente, se segue qualcosa sull'ordine generale del modello Von Neumann e fornisce almeno una quantità minima ragionevole di memoria, che dovrebbe essere sufficiente per consentire un'implementazione in C. Per un'implementazione "ospitata" (una che gira su un sistema operativo) è necessario supportare alcune nozioni che corrispondono ragionevolmente ai file e avere un set di caratteri con un determinato set minimo di caratteri (sono richiesti 91).
Il secondo significa che dovrebbe essere possibile scrivere codice che manipola direttamente l'hardware, in modo da poter scrivere cose come boot loader, sistemi operativi, software incorporato che funziona senza alcun sistema operativo, ecc. Alla fine ci sono alcuni limiti in questo senso, quindi quasi tutti è probabile che il sistema operativo pratico, il boot loader, ecc. contenga almeno un po 'di codice scritto nel linguaggio assembly. Allo stesso modo, anche un piccolo sistema incorporato probabilmente includerà almeno una sorta di routine di libreria pre-scritte per consentire l'accesso ai dispositivi sul sistema host. Sebbene sia difficile definire un limite preciso, l'intento è che la dipendenza da tale codice sia ridotta al minimo.
Il comportamento indefinito nella lingua è in gran parte guidato dall'intenzione che la lingua supporti queste capacità. Ad esempio, la lingua consente di convertire un numero intero arbitrario in un puntatore e di accedere a qualunque cosa si trovi a quell'indirizzo. Lo standard non tenta di dire cosa accadrà quando lo fai (ad esempio, anche la lettura da alcuni indirizzi può avere effetti visibili esternamente). Allo stesso tempo, non cerca di impedirti di fare queste cose, perché è necessario per alcuni tipi di software che dovresti essere in grado di scrivere in C.
C'è un comportamento indefinito guidato anche da altri elementi di design. Ad esempio, un altro intento di C è supportare la compilazione separata. Ciò significa (ad esempio) che si intende che è possibile "collegare" pezzi insieme utilizzando un linker che segue approssimativamente quello che la maggior parte di noi vede come il solito modello di linker. In particolare, dovrebbe essere possibile combinare moduli compilati separatamente in un programma completo senza conoscenza della semantica della lingua.
Esiste un altro tipo di comportamento indefinito (che è molto più comune in C ++ rispetto a C), che è presente semplicemente a causa dei limiti della tecnologia del compilatore: cose che sostanzialmente sappiamo essere errori e che probabilmente il compilatore dovrebbe diagnosticare come errori, ma dati gli attuali limiti della tecnologia del compilatore, è dubbio che possano essere diagnosticati in ogni circostanza. Molti di questi sono guidati da altri requisiti, come ad esempio la compilazione separata, quindi si tratta in gran parte di bilanciare requisiti contrastanti, nel qual caso il comitato ha generalmente optato per supportare maggiori capacità, anche se ciò significa mancanza di diagnosi di alcuni possibili problemi, piuttosto che limitare le capacità per garantire che tutti i possibili problemi vengano diagnosticati.
Queste differenze di intenti guidano la maggior parte delle differenze tra C e qualcosa come Java o i sistemi basati su CLI di Microsoft. Questi ultimi sono abbastanza esplicitamente limitati a lavorare con un set molto più limitato di hardware o richiedono software per emulare l'hardware più specifico a cui si rivolgono. Intendono anche specificamente impedire qualsiasi manipolazione diretta dell'hardware, richiedendo invece di utilizzare qualcosa come JNI o P / Invoke (e il codice scritto in qualcosa come C) per fare un simile tentativo.
Ritornando ai teoremi di Godel per un momento, possiamo tracciare qualcosa di simile: Java e CLI hanno optato per l'alternativa "internamente coerente", mentre C ha optato per l'alternativa "completa". Naturalmente, questa è un'analogia molto approssimativa - dubito che chiunque di tentare una prova formale sia coerenza interna o la completezza in entrambi i casi. Tuttavia, l'idea generale si adatta abbastanza bene alle scelte che hanno preso.