A maioria dos tipos de UB com os quais geralmente nos preocupamos, como NULL-deref ou dividir por zero, são UB em tempo de execução . Compilar uma função que causaria UB em tempo de execução, se executada, não deve causar a falha do compilador. A menos que possa provar que a função (e aquele caminho através da função) definitivamente será executada pelo programa.
(2ª reflexão: talvez eu não tenha considerado a avaliação necessária do template / constexpr em tempo de compilação. Possivelmente UB durante isso pode causar estranheza arbitrária durante a tradução, mesmo se a função resultante nunca for chamada.)
O comportamento durante a tradução da citação ISO C ++ na resposta de @ StoryTeller é semelhante à linguagem usada no padrão ISO C. C não inclui modelos ou avaliação constexpr
obrigatória em tempo de compilação.
Mas curiosidade : ISO C diz em uma nota que se a tradução for encerrada, deve ser com uma mensagem de diagnóstico. Ou "comportando-se durante a tradução ... de maneira documentada". Não acho que "ignorar a situação completamente" possa ser lido como incluindo a interrupção da tradução.
Resposta antiga, escrita antes de eu aprender sobre o tempo de tradução UB. É verdade para o runtime-UB, entretanto, e, portanto, ainda potencialmente útil.
Não existe UB que aconteça em tempo de compilação. Ele pode ser visível para o compilador ao longo de um determinado caminho de execução, mas em termos de C ++ não aconteceu até que a execução alcance esse caminho de execução por meio de uma função.
Defeitos em um programa que tornam impossível até mesmo compilar não são UB, são erros de sintaxe. Esse programa "não é bem formado" na terminologia C ++ (se eu entendi corretamente o padrão). Um programa pode ser bem formado, mas conter UB. Diferença entre comportamento indefinido e mal formado, nenhuma mensagem de diagnóstico necessária
A menos que eu esteja entendendo mal alguma coisa, ISO C ++ requer que este programa seja compilado e executado corretamente, porque a execução nunca atinge a divisão por zero. (Na prática ( Godbolt ), bons compiladores apenas fazem executáveis funcionais. Gcc / clang avisa sobre x / 0
isso, mas não, mesmo durante a otimização. Mas de qualquer forma, estamos tentando dizer o quão baixo ISO C ++ permite que a qualidade de implementação seja. Portanto, verificar o gcc / clang dificilmente é um teste útil além de confirmar que escrevi o programa corretamente.)
int cause_UB() {
int x=0;
return 1 / x;
}
int main(){
if (0)
cause_UB();
}
Um caso de uso para isso pode envolver o pré-processador C, ou constexpr
variáveis e ramificações nessas variáveis, o que leva a um absurdo em alguns caminhos que nunca são alcançados para essas escolhas de constantes.
Caminhos de execução que causam UB visível no tempo de compilação podem ser assumidos como nunca tomados, por exemplo, um compilador para x86 poderia emitir um ud2
(causar exceção de instrução ilegal) como a definição para cause_UB()
. Ou dentro de uma função, se um lado de um if()
levar a UB comprovável , o ramal pode ser removido.
Mas o compilador ainda precisa compilar todo o resto de uma maneira sã e correta. Todos os caminhos que não encontram (ou não podem ser encontrados) UB ainda devem ser compilados para asm que executa como se a máquina abstrata C ++ o estivesse executando.
Você poderia argumentar que o UB visível no tempo de compilação incondicional main
é uma exceção a essa regra. Ou, de outra forma, comprovável em tempo de compilação que a execução começando main
em de fato atinge UB garantido.
Eu ainda argumentaria que os comportamentos legais do compilador incluem a produção de uma granada que explode se for executada. Ou, mais plausivelmente, uma definição de main
que consiste em uma única instrução ilegal. Eu diria que, se você nunca executou o programa, ainda não houve nenhum UB. O compilador em si não pode explodir, IMO.
Funções contendo UB possíveis ou prováveis dentro dos ramos
O UB ao longo de qualquer caminho de execução volta no tempo para "contaminar" todo o código anterior. Mas, na prática, os compiladores só podem tirar proveito dessa regra quando podem realmente provar que os caminhos de execução levam a UB visível no tempo de compilação. por exemplo
int minefield(int x) {
if (x == 3) {
*(char*)nullptr = x/0;
}
return x * 5;
}
O compilador deve fazer um conjunto que funcione para todos os x
outros que não 3, até os pontos em que x * 5
causa estouro de sinal UB em INT_MIN e INT_MAX. Se esta função nunca for chamada com x==3
, o programa obviamente não contém UB e deve funcionar como escrito.
Poderíamos também ter escrito if(x == 3) __builtin_unreachable();
em GNU C para dizer ao compilador que x
definitivamente não é 3.
Na prática, há código de "campo minado" em todos os lugares em programas normais. por exemplo, qualquer divisão por um inteiro promete ao compilador que é diferente de zero. Qualquer deref de ponteiro promete ao compilador que não é NULL.