Por que main não retorna 0 aqui?

116

Eu só estava lendo

ISO / IEC 9899: 201x Minuta do Comitê - 12 de abril de 2011

em que encontrei em 5.1.2.2.3 Término do programa

..reaching the } that terminates the main function returns a value of 0. 

isso significa que se você não especificar nenhuma instrução return em main()e se o programa for executado com êxito, a chave de fechamento} de main retornará 0.

Mas no código a seguir eu não especifico nenhuma instrução de retorno, ainda que não retorne 0

#include<stdio.h>
int sum(int a,int b)
{
return (a + b);
}

int main()
{
    int a=10;
    int b=5;
    int ans;    
    ans=sum(a,b);
    printf("sum is %d",ans);
}

compilar

gcc test.c  
./a.out
sum is 15
echo $?
9          // here it should be 0 but it shows 9 why?
Jeegar Patel
fonte
69
+1 por ter paciência para ler as especificações ...
Asher
16
gccpor si só (para a versão 4.6.2) compila uma linguagem muito semelhante, mas não exatamente como C. Ele compila o GnuC89 - uma linguagem "vagamente" baseada no C89.
pmg
2
Os parênteses na returndeclaração em sum()são desnecessários. int main()deveria ser int main(void).
Keith Thompson
24
Confusão! = Erro de digitação. No meu teclado, '0' e 'o' são próximos o suficiente para que sejam facilmente o último. ;-)
The111
2
IMHO é uma especificação bastante estúpida, já que força o compilador a gerenciar a função "principal" de uma maneira especial, adicionando um "return 0" implícito. Portanto, uma função chamada "principal" se comporta de maneira ligeiramente diferente. E sobre as verificações em tempo de compilação ("nenhum valor de retorno" um semelhante)?
Giuseppe Guerrini

Respostas:

141

Essa regra foi adicionada na versão de 1999 do padrão C. No C90, o status retornado é indefinido.

Você pode habilitá-lo passando -std=c99para o gcc.

Como uma nota lateral, curiosamente 9 é retornado porque é o retorno do printfqual apenas escreveu 9 caracteres.

cnicutar
fonte
40
Ou você pode simplesmente adicionar return 0;antes do fechamento }. É inofensivo e torna seu programa portátil para compiladores mais antigos.
Keith Thompson
2
@ Sr.32: boa observação, printf () retorna o comprimento da string, então é 9 que é o "retorno" do principal (sem usar -std = c99).
Hicham
1
@cnicutar: As funções normalmente não retornam pequenos valores na pilha - porque envolveria um pop, um push e um salto ao invés de apenas um movimento e um retorno - então é quase certo um registro, eaxespecificamente no x86.
Jon Purdy
8
Sim, a API x86 geralmente retorna um valor semelhante a um inteiro por meio do eaxregistro. Consulte en.wikipedia.org/wiki/X86_calling_conventions#cdecl para obter mais informações.
Sylvain Defresne
2
Várias vezes eu vi um código como "int foo (void) {bar ();}", onde "return bar ()" se pretendia. Este código funciona bem na maioria dos processadores, apesar do bug óbvio.
ugoren
15

Ele retorna o valor de retorno do printfqual é o número de caracteres realmente impressos.

Summer_More_More_Tea
fonte
a pergunta não fala sobre o que está impresso no console ao executar o programa, ela fala sobre o valor de retorno do programa: (você pode obtê-lo no linux com o comando schell: echo $? e no windows com: echo% errorlevel% )
Hicham
1
@eharvest Embora eu não saiba como verificar %errorlevel%no Windows, há alguma diferença entre o código de saída e o valor de retorno do mainno linux?
Summer_More_More_Tea
1
@eharvest: A resposta não fala sobre o que está impresso no console também. Ele fala sobre o valor de retorno,printf que neste caso é 9, que então é "promovido" como o código de saída de mainalguma forma ao usar certas versões do gcc.
AH
Desculpe. meu erro. você está certo. eu li esta resposta muito rápido: /
Hicham
1
Observe que isso não é garantido. No C89 / C90, o status retornado neste caso é indefinido ; pode ser qualquer coisa. Acontece para retornar 9 porque o compilador não fez nenhum esforço para retornar mais nada. Outros compiladores provavelmente se comportarão de maneira diferente.
Keith Thompson
6

O valor de retorno de uma função é normalmente armazenado no registrador eax da cpu, portanto, a instrução "return 4;" normalmente compilaria para

mov eax, 4;
ret;

e o retorno x (dependendo do seu compilador) seria algo como:

mov eax, [ebp + 4];
ret;

se você não especificar um valor de retorno, o compilador ainda emitirá o "ret", mas não alterará o valor de eax. Portanto, o chamador pensará que o que foi deixado no registrador eax anteriormente é o valor de retorno. Para este exemplo, normalmente seria o valor de retorno printf, mas diferentes compiladores irão gerar diferentes códigos de máquina e usar alguns registradores de forma diferente.

Esta é uma explicação simplificada, diferentes convenções de chamada e plataformas de destino desempenharão um papel vital, mas deve ser informação suficiente para explicar o que está acontecendo 'nos bastidores' em seu exemplo.

Se você tem um conhecimento básico de assembler, vale a pena comparar a desmontagem de diferentes compiladores. Você pode descobrir que alguns compiladores estão limpando o registro eax como uma proteção.

noggin182
fonte
11
O compilador pode fazer isso. É indefinido. Em vez disso, ele pode optar por atirar em sua cabeça com um cartucho de impressora.
Lightness Races in Orbit
@LightnessRacesinOrbit undefined não é o mesmo que imprevisível, ou seja, de "se o compilador fizer X, ele estará em conformidade com o padrão", não segue logicamente "o compilador pode fazer X"
Owen
@Owen: Cite a passagem padrão que define isso.
Lightness Races in Orbit
2
@LightnessRacesinOrbit Desculpe, não entendi direito. Acho que o que realmente quero dizer é que acho que os insights básicos, como noggin182, fornecidos nesta resposta, são incrivelmente úteis para depuração. Quando seu programa está produzindo resultados inesperados, muitas vezes você não sabe de onde eles estão vindo ou mesmo onde procurar no código, e ter uma compreensão dos detalhes de implementação pode indicar a direção certa.
Owen
@Owen, eu nunca afirmei o contrário
Lightness Races in Orbit