Primeiro, observarei que, embora apenas mencione "C" aqui, o mesmo se aplica igualmente ao C ++.
O comentário mencionando Godel estava parcialmente (mas apenas parcialmente) em questão.
Quando você começar a ele, um comportamento indefinido nas normas C é em grande parte apenas apontando a fronteira entre o que as tentativas padrão para definir, e que isso não acontece.
Os teoremas de Godel (existem dois) dizem basicamente que é impossível definir um sistema matemático que possa ser provado (por suas próprias regras) completo e consistente. Você pode fazer suas regras para que sejam completas (o caso com o qual ele lidou foram as regras "normais" para números naturais), ou então você pode tornar possível provar sua consistência, mas não pode ter as duas.
No caso de algo como C, isso não se aplica diretamente - na maioria das vezes, a "provabilidade" da integridade ou consistência do sistema não é uma alta prioridade para a maioria dos designers de idiomas. Ao mesmo tempo, sim, eles provavelmente foram influenciados (pelo menos até certo ponto) por saber que é comprovadamente impossível definir um sistema "perfeito" - que seja comprovadamente completo e consistente. Saber que tal coisa é impossível pode ter tornado um pouco mais fácil dar um passo para trás, respirar um pouco e decidir sobre os limites do que eles tentariam definir.
Correndo o risco de (mais uma vez) ser acusado de arrogância, eu caracterizaria o padrão C como sendo governado (em parte) por duas idéias básicas:
- O idioma deve suportar a maior variedade de hardware possível (idealmente, todo o hardware "sadio" até um limite inferior razoável).
- O idioma deve oferecer suporte à escrita da maior variedade de software possível para o ambiente especificado.
O primeiro significa que, se alguém definir uma nova CPU, deve ser possível fornecer uma implementação boa, sólida e utilizável de C para isso, desde que o design fique pelo menos razoavelmente próximo de algumas diretrizes simples - basicamente, se segue algo na ordem geral do modelo de Von Neumann e fornece pelo menos uma quantidade mínima razoável de memória, que deve ser suficiente para permitir uma implementação em C. Para uma implementação "hospedada" (que é executada em um sistema operacional), é necessário oferecer suporte a alguma noção que corresponda razoavelmente aos arquivos e ter um conjunto de caracteres com um determinado conjunto mínimo de caracteres (91 são necessários).
O segundo significa que deve ser possível escrever um código que manipule o hardware diretamente, para que você possa escrever coisas como gerenciadores de inicialização, sistemas operacionais, software incorporado que é executado sem nenhum SO, etc. Existem alguns limites nesse sentido, quase todos os sistema operacional prático, carregador de inicialização, etc., provavelmente conterá pelo menos um pouco de código escrito em linguagem assembly. Da mesma forma, mesmo um pequeno sistema incorporado provavelmente incluirá pelo menos algum tipo de rotina de biblioteca pré-escrita para dar acesso aos dispositivos no sistema host. Embora seja difícil definir um limite preciso, a intenção é que a dependência desse código seja mantida no mínimo.
O comportamento indefinido no idioma é amplamente motivado pela intenção do idioma de oferecer suporte a esses recursos. Por exemplo, o idioma permite converter um número inteiro arbitrário em um ponteiro e acessar o que quer que esteja nesse endereço. O padrão não tenta dizer o que acontecerá quando você o fizer (por exemplo, mesmo a leitura de alguns endereços pode ter efeitos visíveis externamente). Ao mesmo tempo, não faz nenhuma tentativa de impedi-lo de fazer essas coisas, porque você precisa de alguns tipos de software que deveria poder escrever em C.
Há algum comportamento indefinido direcionado por outros elementos de design. Por exemplo, uma outra intenção de C é oferecer suporte à compilação separada. Isso significa (por exemplo) que se pretende "vincular" partes usando um vinculador que segue aproximadamente o que a maioria de nós vê como o modelo usual de um vinculador. Em particular, deve ser possível combinar módulos compilados separadamente em um programa completo sem o conhecimento da semântica da linguagem.
Há outro tipo de comportamento indefinido (que é muito mais comum em C ++ que C), que está presente simplesmente devido aos limites da tecnologia do compilador - coisas que basicamente sabemos que são erros e que provavelmente o compilador deve diagnosticar como erros, mas, considerando os limites atuais da tecnologia do compilador, é duvidoso que eles possam ser diagnosticados sob todas as circunstâncias. Muitos desses fatores são motivados por outros requisitos, como compilação separada, portanto é uma questão de equilibrar requisitos conflitantes; nesse caso, o comitê geralmente optou por oferecer suporte a recursos maiores, mesmo que isso signifique falta de diagnóstico de possíveis problemas, em vez de limitar os recursos para garantir que todos os problemas possíveis sejam diagnosticados.
Essas diferenças de intenção conduzem a maioria das diferenças entre C e algo como Java ou sistemas baseados em CLI da Microsoft. Estes últimos são explicitamente limitados a trabalhar com um conjunto de hardware muito mais limitado ou exigir que o software imite o hardware mais específico que eles almejam. Eles também pretendem especificamente impedir qualquer manipulação direta de hardware, exigindo que você use algo como JNI ou P / Invoke (e código escrito em algo como C) para fazer essa tentativa.
Voltando aos teoremas de Godel por um momento, podemos traçar um paralelo: Java e CLI optaram pela alternativa "internamente consistente", enquanto C optou pela alternativa "completa". Claro, isso é uma analogia muito grosseira - Eu duvido que alguém está tentando uma prova formal de qualquer consistência interna ou completude em ambos os casos. No entanto, a noção geral se encaixa bastante de perto com as escolhas que fizeram.