O que é um comportamento indefinido em C e C ++? E o comportamento não especificado e o comportamento definido pela implementação? Qual a diferença entre eles?
530
O que é um comportamento indefinido em C e C ++? E o comportamento não especificado e o comportamento definido pela implementação? Qual a diferença entre eles?
Respostas:
O comportamento indefinido é um daqueles aspectos da linguagem C e C ++ que pode surpreender os programadores vindos de outras linguagens (outras linguagens tentam escondê-lo melhor). Basicamente, é possível escrever programas em C ++ que não se comportam de maneira previsível, mesmo que muitos compiladores em C ++ não relatem erros no programa!
Vejamos um exemplo clássico:
A variável
p
aponta para a string literal"hello!\n"
e as duas atribuições abaixo tentam modificar essa string literal. O que este programa faz? De acordo com a seção 2.14.5, parágrafo 11 do padrão C ++, ele invoca um comportamento indefinido :Eu posso ouvir as pessoas gritando "Mas espere, eu posso compilar isso sem problemas e obter a saída
yellow
" ou "O que você quer dizer com literais de seqüência indefinidos são armazenados na memória somente leitura, para que a primeira tentativa de atribuição resulte em um dump principal". Esse é exatamente o problema do comportamento indefinido. Basicamente, o padrão permite que qualquer coisa aconteça depois que você invoca um comportamento indefinido (mesmo demônios nasais). Se existe um comportamento "correto" de acordo com o seu modelo mental da linguagem, esse modelo está simplesmente errado; O padrão C ++ tem o único voto, ponto final.Outros exemplos de comportamento indefinido incluem acessar uma matriz além de seus limites, desreferenciar o ponteiro nulo , acessar objetos após o término de sua vida útil ou escrever expressões supostamente inteligentes como
i++ + ++i
.A Seção 1.9 do padrão C ++ também menciona os dois irmãos menos perigosos do comportamento indefinido , comportamento não especificado e comportamento definido pela implementação :
Especificamente, a seção 1.3.24 declara:
O que você pode fazer para evitar um comportamento indefinido? Basicamente, você precisa ler bons livros em C ++ de autores que sabem do que estão falando. Parafusos tutoriais da Internet. Bullschildt do parafuso.
fonte
int f(){int a; return a;}
: o valor dea
pode mudar entre as chamadas de função.Bem, isso é basicamente um copiar-colar direto do padrão
fonte
int foo(int x) { if (x >= 0) launch_missiles(); return x << 1; }
um compilador pode determinar que, como todos os meios de invocar a função que não inicia os mísseis invocam o Comportamento indefinido, ele pode tornar a chamadalaunch_missiles()
incondicional.Talvez uma redação fácil possa ser mais fácil de entender do que a definição rigorosa dos padrões.
comportamento definido pela implementação
A linguagem diz que temos tipos de dados. Os fornecedores do compilador especificam quais tamanhos eles devem usar e fornecem uma documentação do que eles fizeram.
comportamento indefinido
Você está fazendo algo errado. Por exemplo, você tem um valor muito grande em um
int
que não se encaixachar
. Como você coloca esse valorchar
? na verdade não tem como! Tudo poderia acontecer, mas a coisa mais sensata seria pegar o primeiro byte desse int e inseri-lochar
. É errado fazer isso para atribuir o primeiro byte, mas é o que acontece sob o capô.comportamento não especificado
Qual função desses dois é executada primeiro?
O idioma não especifica a avaliação, da esquerda para a direita ou da direita para a esquerda! Portanto, um comportamento não especificado pode ou não resultar em um comportamento indefinido, mas certamente seu programa não deve produzir um comportamento não especificado.
@eSKay Acho que vale a pena editar sua resposta para esclarecer mais :)
A diferença entre definido pela implementação e não especificado, é que o compilador deve escolher um comportamento no primeiro caso, mas não no segundo caso. Por exemplo, uma implementação deve ter uma e apenas uma definição de
sizeof(int)
. Portanto, não se pode dizer quesizeof(int)
é 4 para uma parte do programa e 8 para outras. Diferentemente do comportamento não especificado, onde o compilador pode dizer OK, vou avaliar esses argumentos da esquerda para a direita e os argumentos da próxima função são avaliados da direita para a esquerda. Isso pode acontecer no mesmo programa, por isso é chamado de não especificado . De fato, o C ++ poderia ter sido facilitado se alguns comportamentos não especificados fossem especificados. Dê uma olhada aqui na resposta do Dr. Stroustrup para isso :fonte
fun(fun1(), fun2());
não é o comportamento"implementation defined"
? O compilador tem que escolher um ou outro curso, afinal?"I am gonna evaluate these arguments left-to-right and the next function's arguments are evaluated right-to-left"
eu entendo issocan
acontecer. Realmente, com os compiladores que usamos hoje em dia?Do documento oficial de justificativa C
fonte
Comportamento indefinido vs. comportamento não especificado tem uma breve descrição dele.
Seu resumo final:
fonte
Historicamente, o Comportamento Definido pela Implementação e o Comportamento Indefinido representavam situações nas quais os autores da Norma esperavam que as pessoas que escrevessem implementações de qualidade usassem julgamento para decidir quais garantias comportamentais, se houver, seriam úteis para programas no campo de aplicação pretendido em execução no alvos pretendidos. As necessidades do código de processamento de números de ponta são bastante diferentes das do código de sistemas de baixo nível, e o UB e o BID oferecem aos escritores de compiladores flexibilidade para atender a essas diferentes necessidades. Nenhuma categoria determina que as implementações se comportem de uma maneira que seja útil para uma finalidade específica ou mesmo para qualquer finalidade. As implementações de qualidade que afirmam ser adequadas a um propósito específico, no entanto, devem se comportar de maneira adequada a esse objetivose a Norma exige ou não .
A única diferença entre o comportamento definido pela implementação e o comportamento indefinido é que o primeiro exige que as implementações definam e documentem um comportamento consistente, mesmo nos casos em que nada que a implementação possa fazer seria útil . A linha divisória entre eles não é se geralmente seria útil para implementações definir comportamentos (os escritores do compilador devem definir comportamentos úteis quando praticáveis, se o Padrão exige ou não), mas se pode haver implementações em que definir um comportamento seria simultaneamente caro. e inútil . Um julgamento de que tais implementações possam existir não implica, de forma alguma, nenhum julgamento sobre a utilidade de suportar um comportamento definido em outras plataformas.
Infelizmente, desde meados da década de 90, os escritores de compiladores começaram a interpretar a falta de mandatos comportamentais como um julgamento de que as garantias comportamentais não valem o custo, mesmo nos campos de aplicativos onde são vitais e até nos sistemas em que não custam praticamente nada. Em vez de tratar o UB como um convite para exercer um julgamento razoável, os escritores do compilador começaram a tratá-lo como uma desculpa para não fazê-lo.
Por exemplo, dado o seguinte código:
a implementação de um complemento de dois não precisaria fazer nenhum esforço para tratar a expressão
v << pow
como uma mudança de complemento de dois sem considerar sev
era positivo ou negativo.A filosofia preferida entre alguns dos escritores de compiladores de hoje, no entanto, sugeriria que, porque
v
só pode ser negativo se o programa se envolver em Comportamento indefinido, não há razão para que o programa limite o intervalo negativov
. Embora o deslocamento para a esquerda de valores negativos costumava ser suportado em todos os compiladores significativos, e uma grande quantidade de código existente se baseie nesse comportamento, a filosofia moderna interpretaria o fato de o Padrão afirmar que os valores negativos para o deslocamento à esquerda são UB como implicando que os escritores do compilador devem ficar à vontade para ignorá-lo.fonte
<<
ser UB em números negativos é uma pequena armadilha desagradável e fico feliz em ser lembrado disso!i+j>k
gera 1 ou 0 nos casos em que a adição transborda, desde que não tenha outros efeitos colaterais , um compilador poderá fazer algumas otimizações massivas que não seriam possíveis se o programador escrevesse o código como(int)((unsigned)i+j) > k
.Padrão C ++ n3337 § 1.3.10 comportamento definido pela implementação
Às vezes, o C ++ Standard não impõe um comportamento específico em algumas construções, mas diz que um comportamento específico e bem definido deve ser escolhido e descrito por uma implementação específica (versão da biblioteca). Assim, o usuário ainda pode saber exatamente como o programa se comportará, embora o Standard não descreva isso.
Padrão C ++ n3337 § 1.3.24 comportamento indefinido
Quando o programa encontra uma construção que não é definida de acordo com o padrão C ++, é permitido fazer o que quiser (talvez envie um email para mim ou talvez envie um email para você ou ignore completamente o código).
N3337 padrão C ++ § 1.3.25 comportamento não especificado
O C ++ Standard não impõe um comportamento específico em algumas construções, mas diz que um comportamento específico e bem definido deve ser escolhido ( não é necessário descrever o bot ) por uma implementação específica (versão da biblioteca). Portanto, no caso em que nenhuma descrição foi fornecida, pode ser difícil para o usuário saber exatamente como o programa se comportará.
fonte
Implementação definida
Não especificado -
Indefinido-
fonte
uint32_t s;
, avaliar1u<<s
quandos
é 33 pode render 0 ou talvez 2, mas não fazer outra coisa maluca. No entanto, a avaliação de compiladores mais recentes1u<<s
pode fazer com que um compilador determine que, comos
deve ter sido menor que 32, qualquer código antes ou depois dessa expressão que só seria relevante ses
tivesse 32 ou mais pode ser omitido.