Comportamento indefinido em C (Undefined behavior)

Comportamento indefinido

A linguagem C, desenvolvida em 1978 por Dennis Ritchie, é a  linguagem mais usada para a programação nos últimos anos. O site tiobe possui um índice das linguagens mais usadas desde 2001. O índice do mês de setembro de 2013 mostra o seguinte:

 

index Comportamento indefinido

 

Ela também é talvez a linguagem mais popular em sistemas embarcados. Mas por que ela é tão popular? Dentre várias características positivas podemos destacar:

  • Possui poucos comandos (44 no padrão C11) e é relativamente simples de entender e aprender
  • Existem compiladores C portados para a maioria dos processadores
  • É uma linguagem poderosa que gera códigos eficiêntes

 

Sabe-se que arquiteturas diferentes não possuem o mesmo conjunto de instruções. Sendo assim, para que seja possível otimizar e gerar códigos mais eficientes para cada arquitetura, o padrão C, dá uma certa liberdade para os compiladores decidirem o que implementar em casos em que não temos um comportamento único para as arquiteturas.

 

Um exemplo simples é a divisão por zero.

 

 

Na arquitetura x86, teremos uma exceção por harware se o valor de b é zero, porém na arquitetura PowerPC, essa instrução é apenas ignorada. Esse exemplo é chamado de comportamento indefinido, em inglês (undefined behavior) uma vez que o compilador C tem mais de uma escolha para implementar. Por isso o padrão deixa aberto ao compilador gerar o código mais eficiente para cada arquitetura.
Neste mesmo exemplo, o compilador poderia implementar a função retornando zero, qualquer outro valor ou ainda na arquitetura PowerPC não fazer nada.
 
Um outro exemplo, agora real, usado no banco de dados PostgreeSQL: 
 

 

Neste exemplo, a mensagem "division by zero" nunca irá aparecer mesmo se arg2 = 0. Isso acontece porque o compilador conclui que se arg2 é igual a zero, a divisão em 

 

PG_RETURN_INT32((int32) arg1 / arg2)

 

sempre resultará num comportamento indefinido. Então neste caso o compilador decidiu por si só optimizar o código de forma que essa parte só execute quando arg2 é diferente de zero e não gerar o código para a chamada da função ereport.

 
A verdade é que a função ereport nunca retorna, e o programador concluiu (erroneamente) que se arg2 é igual a zero, a função seria chamada e nunca mais retornaria e a divisão por zero seria evitado. Porém esse detalhe de implementação não foi passado ao compilador. Uma solução para esse exemplo seria colocar um else.
 
Logo o programador deve evitar de concluir o que o compilador vai gerar como código e sempre que possível explicitar o que se deseja que o programa faça. Também deve-se prestar muita atenção quando troca-se de compilador e a otimização está habilitada principalmente quando um código é portado de uma arquitetura à outra.
 
Fontes:

NEWSLETTER

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Obrigado! Sua inscrição foi um sucesso.

Ops, algo deu errado. Por favor tente novamente.

Marcelo Jo
Engenheiro eletrônico com 10 anos de experiência em sistemas embarcados, pós graduado em redes de computadores e atualmente cursando mestrado em sistemas de visão por computador na universidade Laval no Canadá. Compartilha seu conhecimento neste portal quando tem tempo livre e quando não está curtindo a vida com sua mulher e os 3 filhos.

2
Deixe um comentário

avatar
 
2 Comment threads
0 Thread replies
0 Followers
 
Most reacted comment
Hottest comment thread
2 Comment authors
Rogerio MachadoMatheus Quick Recent comment authors
  Notificações  
recentes antigos mais votados
Notificar
Matheus Quick
Visitante
Matheus Quick

linguagem C ainda é muito usada, muito boa, mesmo sendo antiga.

Rogerio Machado
Membro
Rogerio Machado

Interessante que se fosse C++ "ereport()" poderia gerar uma excecao(throw) e o codigo seria perfeitamente correto porque nao executaria a divisao por 0.