Eu me deparei com uma pergunta interessante em um fórum há muito tempo e quero saber a resposta.
Considere a seguinte função C:
f1.c
#include <stdbool.h>
bool f1()
{
int var1 = 1000;
int var2 = 2000;
int var3 = var1 + var2;
return (var3 == 0) ? true : false;
}
Isso sempre deve retornar false
desde então var3 == 3000
. A main
função fica assim:
main.c
#include <stdio.h>
#include <stdbool.h>
int main()
{
printf( f1() == true ? "true\n" : "false\n");
if( f1() )
{
printf("executed\n");
}
return 0;
}
Como f1()
sempre deve retornar false
, seria de esperar que o programa imprimisse apenas um falso na tela. Mas depois de compilá-lo e executá-lo, executado também é exibido:
$ gcc main.c f1.c -o test
$ ./test
false
executed
Por que é que? Esse código tem algum tipo de comportamento indefinido?
Nota: Eu compilei com gcc (Ubuntu 4.9.2-10ubuntu13) 4.9.2
.
f1()
para o mesmo arquivomain()
, obteria alguma estranheza: embora seja correto em C ++ usar()
para uma lista de parâmetros vazia, em C usado para uma função com uma lista de parâmetros ainda não definida ( basicamente espera uma lista de parâmetros no estilo K&R após o)
). Para estar correto C, você deve alterar seu código parabool f1(void)
.main()
poderia ser simplificada paraint main() { puts(f1() == true ? "true" : "false"); puts(f1() ? "true" : "false"); return 0; }
- este iria mostrar a discrepância melhor.void
?true
efalse
na K&R 1st ed., portanto não havia tais problemas. Era apenas 0 e diferente de zero para verdadeiro e falso. Não é? Não sei se os protótipos estavam disponíveis naquele momento._Bool
tipo nem<stdbool.h>
cabeçalho.Respostas:
Conforme observado em outras respostas, o problema é que você usa
gcc
sem opções de compilador definidas. Se você fizer isso, o padrão será o que é chamado "gnu90", que é uma implementação não padrão do antigo padrão C90 retirado de 1990.No antigo padrão C90, havia uma falha importante na linguagem C: se você não declarasse um protótipo antes de usar uma função, o padrão seria
int func ()
(onde( )
significa "aceitar qualquer parâmetro"). Isso altera a convenção de chamada da funçãofunc
, mas não altera a definição real da função. Como o tamanhobool
e o tamanhoint
são diferentes, seu código chama um comportamento indefinido quando a função é chamada.Esse perigoso comportamento sem sentido foi corrigido no ano de 1999, com o lançamento do padrão C99. Declarações implícitas de funções foram banidas.
Infelizmente, o GCC até a versão 5.xx ainda usa o padrão C antigo por padrão. Provavelmente, não há razão para você querer compilar seu código como algo que não seja o padrão C. Portanto, você deve dizer explicitamente ao GCC que ele deve compilar seu código como código C moderno, em vez de porcaria GNU não padrão de 25 anos ou mais .
Corrija o problema sempre compilando seu programa como:
-std=c11
diz para fazer uma tentativa tímida de compilar de acordo com o padrão C (atual) (informalmente conhecido como C11).-pedantic-errors
diz para ele fazer de todo o coração o descrito acima e fornecer erros do compilador quando você escreve um código incorreto que viola o padrão C.-Wall
significa dar-me alguns avisos extras que podem ser bons.-Wextra
significa dar-me outros avisos extras que podem ser bons.fonte
-std=gnu11
é muito mais provável que funcione conforme o esperado-std=c11
, devido a qualquer um ou a todos: necessitar de funcionalidade de biblioteca além do C11 (POSIX, X / Open, etc) disponível no "gnu" modos estendidos, mas suprimidos no modo estrito de conformidade; bugs nos cabeçalhos do sistema que estão ocultos nos modos estendidos, por exemplo, assumindo a disponibilidade de typedefs fora do padrão; uso não intencional de trigrafs (esse recurso incorreto padrão é desativado no modo "gnu").-pedantic-errors
é menos problemático do que-Werror
mas pode e pode causar a falha na compilação de programas em sistemas operacionais que não estão incluídos no teste do autor original, mesmo quando não há nenhum problema real.bool
/ padrão_Bool
, pode escrever seu código C de maneira "C ++-esque", onde você assume que todas as comparações e operadores lógicos retornambool
como C ++, mesmo que retornemint
em C, por razões históricas . Isso tem a grande vantagem de poder usar ferramentas de análise estática para verificar a segurança de tipo de todas essas expressões e expor todos os tipos de erros em tempo de compilação. É também uma maneira de expressar intenção na forma de código de auto-documentação. E menos importante, ele também economiza alguns bytes de RAM.Você não tem um protótipo declarado
f1()
em main.c, portanto ele é implicitamente definido comoint f1()
, o que significa que é uma função que pega um número desconhecido de argumentos e retorna umint
.Se
int
e tiverbool
tamanhos diferentes, isso resultará em um comportamento indefinido . Por exemplo, na minha máquina,int
tem 4 bytes ebool
um byte. Como a função está definida para retornarbool
, ela coloca um byte na pilha quando retorna. No entanto, como é declarado implicitamente para retornarint
do main.c, a função de chamada tentará ler 4 bytes da pilha.As opções padrão dos compiladores no gcc não indicam que ele está fazendo isso. Mas se você compilar
-Wall -Wextra
, obterá o seguinte:Para corrigir isso, adicione uma declaração para
f1
em main.c, antesmain
:Observe que a lista de argumentos está explicitamente definida como
void
, o que informa ao compilador que a função não aceita argumentos, em oposição a uma lista de parâmetros vazia, o que significa um número desconhecido de argumentos. A definiçãof1
em f1.c também deve ser alterada para refletir isso.fonte
-Werror-implicit-function-declaration
às opções do GCC, dessa forma, esse já não passa despercebido. Uma opção ainda melhor é-Werror
transformar todos os avisos em erros. Força você a corrigir todos os avisos quando eles aparecerem.Acho interessante ver onde realmente ocorre a diferença de tamanho mencionada na excelente resposta de Lundin.
Se você compilar
--save-temps
, obterá arquivos de montagem que podem ser vistos. Aqui está a parte ondef1()
faz a== 0
comparação e retorna seu valor:A parte retornada é
sete %al
. Nas convenções de chamada x86 de C, retornam valores de 4 bytes ou menos (que incluemint
ebool
) são retornados via registrador%eax
.%al
é o byte mais baixo de%eax
. Portanto, os 3 bytes superiores de%eax
são deixados em um estado não controlado.Agora em
main()
:Este verifica se o conjunto de
%eax
é zero, porque acha que está testando um int.A adição de uma declaração de função explícita muda
main()
para:que é o que queremos.
fonte
Por favor compile com um comando como este:
Resultado:
Com essa mensagem, você deve saber o que fazer para corrigi-la.
Edit: Depois de ler um comentário (agora excluído), tentei compilar seu código sem os sinalizadores. Bem, isso me levou a erros do vinculador sem avisos do compilador, em vez de erros do compilador. E esses erros de vinculação são mais difíceis de entender; portanto, mesmo que
-std-gnu99
não seja necessário, tente sempre usar pelo menos-Wall -Werror
isso poupará muita dor na bunda.fonte