Por que uma função sem parâmetros (em comparação com a definição real da função) é compilada?

388

Acabei de encontrar o código C de alguém que estou confuso sobre o motivo de ele estar compilando. Existem dois pontos que não entendo.

Primeiro, o protótipo da função não possui parâmetros em comparação com a definição real da função. Segundo, o parâmetro na definição da função não tem um tipo.

#include <stdio.h>

int func();

int func(param)
{
    return param;
}

int main()
{
    int bla = func(10);    
    printf("%d", bla);
}

Por que isso funciona? Eu testei em alguns compiladores e funciona bem.

AdmiralJonB
fonte
77
É K&R C. Nós escrevemos códigos como esse na década de 1980 antes de haver protótipos de funções completas.
hughdbrown
6
O gcc avisa -Wstrict-prototypestanto para a declaração int func()como int main(): xc: 3: warning: function: não é um protótipo. Você deve declarar main()como main(void)bem.
Jens
3
@Jens Por que você editou a pergunta? Você parece ter perdido o ponto ...
dlras2
11
Acabei de tornar explícito o int implícito. Como isso está errado? Eu acredito que o ponto é por que int func();é compatível com int func(arglist) { ... }.
Jens
3
@MatsPetersson Isso está errado. C99 5.1.2.2.1 contradiz expressamente sua reivindicação e afirma que a versão sem argumento é int main(void).
Jens

Respostas:

271

Todas as outras respostas estão corretas, mas apenas para conclusão

Uma função é declarada da seguinte maneira:

  return-type function-name(parameter-list,...) { body... }

tipo de retorno é o tipo de variável que a função retorna. Não pode ser um tipo de matriz ou um tipo de função. Se não for fornecido, então int é assumido .

nome da função é o nome da função.

parameter-list é a lista de parâmetros que a função usa separados por vírgulas. Se nenhum parâmetro for fornecido, a função não aceita nenhum e deve ser definida com um conjunto de parênteses vazio ou com a palavra-chave void. Se nenhum tipo de variável estiver na frente de uma variável na lista de parâmetros, então int é assumido . Matrizes e funções não são passadas para funções, mas são automaticamente convertidas em ponteiros. Se a lista terminar com reticências (, ...), não haverá um número definido de parâmetros. Nota: o cabeçalho stdarg.h pode ser usado para acessar argumentos ao usar reticências.

E novamente por uma questão de completude. Na especificação C11 6: 11: 6 (página: 179)

O uso de declaradores de função com parênteses vazios (não declaradores de tipo de parâmetro no formato de protótipo) é um recurso obsoleto .

Krishnabhadra
fonte
2
"Se nenhum tipo de variável estiver na frente de uma variável na lista de parâmetros, então int é assumido." Vejo isso no seu link fornecido, mas não consigo encontrá-lo em nenhum padrão c89, c99 ... Você pode fornecer outra fonte?
godaygo
"Se nenhum parâmetro for fornecido, a função não aceita nenhum e deve ser definida com um conjunto de parênteses vazio" parece contraditório com a resposta de Tony The Lion, mas eu acabei de saber que a resposta de Tony The Lion está correta da maneira mais difícil.
jakun
160

Em C func()significa que você pode passar qualquer número de argumentos. Se você não quer argumentos, deve declarar como func(void). O tipo que você está passando para a sua função, se não especificado, é padronizado como int.

Tony o leão
fonte
3
Na verdade, não há um tipo único padrão para int. De fato, todos os argumentos padrão são int (mesmo o tipo de retorno). É perfeitamente aceitável chamar func(42,0x42);(onde todas as chamadas devem usar o formulário de dois argumentos).
Jens
11
Em C, é bastante comum que tipos não especificados tenham como padrão int, por exemplo, quando você declara uma variável: unsigned x; Que tipo é a variável x? Acontece que seu int não assinado
bitek
4
Eu prefiro dizer que int implícito era comum nos velhos tempos. Não é certamente mais tempo e em C99 foi removido a partir de C.
Jens
2
Pode valer a pena notar que esta resposta se aplica a protótipos de funções com listas de parâmetros vazias, mas não a definições de funções com listas de parâmetros vazias.
Autistic
@mnemonicflow que não é um "tipo não especificado padrão para int"; unsignedé apenas outro nome para o mesmo tipo que unsigned int.
Hbbs
58

int func();é uma declaração de função obsoleta dos dias em que não havia um padrão C, ou seja, os dias da K&R C (antes de 1989, ano em que o primeiro padrão "ANSI C" foi publicado).

Lembre-se de que não havia protótipos no K&R C e a palavra-chave voidainda não foi inventada. Tudo o que você pôde fazer foi informar o compilador sobre o tipo de retorno de uma função. A lista de parâmetros vazia no K&R C significa "um número não especificado, mas fixo" de argumentos. Significa que você deve chamar a função com o fixo mesmo número de argumentos de cada vez (ao contrário de um variádica função como printf, onde o número e tipo pode variar para cada chamada).

Muitos compiladores diagnosticam essa construção; em particular gcc -Wstrict-prototypes, dirá que "a declaração de função não é um protótipo", o que é óbvio, porque parece um protótipo (especialmente se você está envenenado por C ++!), mas não é. É uma declaração de tipo de retorno K&R C de estilo antigo.

Regra geral: nunca deixe uma declaração vazia da lista de parâmetros em branco, use int func(void)para ser específico. Isso transforma a declaração de tipo de retorno K&R em um protótipo C89 adequado. Compiladores estão felizes, desenvolvedores estão felizes, verificadores estáticos estão felizes. Aqueles enganados por ^ W ^ Wfond de C ++ podem se encolher, no entanto, porque precisam digitar caracteres extras quando tentam exercitar suas habilidades em idiomas estrangeiros :-)

Jens
fonte
11
Por favor, não relacione isso com C ++. É senso comum que a lista de argumentos vazia não significa parâmetros , e as pessoas do mundo não estão interessadas em lidar com os erros cometidos pela K&R há 40 anos. Mas especialistas em comitês continuam arrastando escolhas contranaturais - para C99, C11.
Pfalcon
11
@pfalcon O senso comum é bastante subjetivo. O que é senso comum para uma pessoa é pura insanidade para outras. Programadores profissionais C sabem que listas de parâmetros vazias indicam um número não especificado, mas fixo de argumentos. Mudar isso provavelmente quebraria muitas implementações. Culpar comitês por evitar mudanças silenciosas está latindo na árvore errada, IMHO.
Jens
53
  • A lista de parâmetros vazia significa "qualquer argumento", portanto a definição não está errada.
  • O tipo ausente é assumido como sendo int.

Eu consideraria qualquer compilação que passa isso com falta de nível de aviso / erro configurado, porém, não há sentido em permitir isso para o código real.

descontrair
fonte
30

É declaração e definição de função no estilo K&R . Do padrão C99 (ISO / IEC 9899: TC3)

Seção 6.7.5.3 Declaradores de função (incluindo protótipos)

Uma lista de identificadores declara apenas os identificadores dos parâmetros da função. Uma lista vazia em um declarador de função que faz parte de uma definição dessa função especifica que a função não possui parâmetros. A lista vazia em um declarador de função que não faz parte de uma definição dessa função especifica que nenhuma informação sobre o número ou tipos de parâmetros é fornecida. (Se os dois tipos de função forem "estilo antigo", os tipos de parâmetro não serão comparados.)

Seção 6.11.6 Declaradores de função

O uso de declaradores de função com parênteses vazios (não declaradores de tipo de parâmetro no formato de protótipo) é um recurso obsoleto.

Seção 6.11.7 Definições de funções

O uso de definições de função com identificadores de parâmetro e listas de declaração separados (não tipo de parâmetro no formato de protótipo e declaradores de identificador) é um recurso obsoleto.

Que o velho estilo significa K & R estilo

Exemplo:

Declaração: int old_style();

Definição:

int old_style(a, b)
    int a; 
    int b;
{
     /* something to do */
}
Lei Mou
fonte
16

C assume intse nenhum tipo é fornecido no tipo de retorno da função e na lista de parâmetros . Somente para esta regra é possível seguir coisas estranhas.

Uma definição de função se parece com isso.

int func(int param) { /* body */}

Se é um protótipo que você escreve

int func(int param);

No protótipo, você pode especificar apenas o tipo de parâmetros. O nome dos parâmetros não é obrigatório. assim

int func(int);

Além disso, se você não especificar o tipo de parâmetro, mas o nome intserá assumido como tipo.

int func(param);

Se você for mais longe, o seguinte também funciona.

func();

O compilador assume int func()quando você escreve func(). Mas não coloque func()dentro de um corpo funcional. Isso será uma chamada de função

Shiplu Mokaddim
fonte
3
Uma lista de parâmetros vazia não está relacionada ao tipo int implícito. int func()não é uma forma implícita de int func(int).
John Bartholomew
11

Conforme afirmado @Krishnabhadra, todas as respostas anteriores de outros usuários têm uma interpretação correta e eu só quero fazer uma análise mais detalhada de alguns pontos.

No Old-C, como no ANSI-C, o " parâmetro formal sem tipo " leva em consideração a dimensão do seu registro de trabalho ou a capacidade de profundidade das instruções (registros de sombra ou ciclo cumulativo de instruções), em uma MPU de 8 bits, será um int16, em 16 bits. MPU e assim será um int16 e assim por diante, no caso de arquiteturas de 64 bits podem optar por compilar opções como: -m32.

Embora pareça uma implementação mais simples em alto nível, para passar vários parâmetros, o trabalho do programador na etapa do tipo de dados de controle de dimensão torna-se mais exigente.

Em outros casos, para algumas arquiteturas de microprocessadores, os compiladores ANSI customizados utilizaram alguns desses recursos antigos para otimizar o uso do código, forçando a localização desses "parâmetros formais não digitados" para trabalhar dentro ou fora do registro de trabalho, hoje você obtém quase o mesmo com o uso de "volátil" e "registrar".

Mas deve-se notar que os compiladores mais modernos não fazem distinção entre os dois tipos de declaração de parâmetros.

Exemplos de uma compilação com o gcc no linux:

main.c

main2.c

main3.c  
De qualquer forma, a declaração do protótipo localmente não é útil, porque não há chamada sem parâmetros, a referência a este protótipo será negligente. Se você usar o sistema com "parâmetro formal sem tipo", para uma chamada externa, continue gerando um tipo de dados declarativo de protótipo.

Como isso:

int myfunc(int param);
RTOSkit
fonte
5
Eu tive o cuidado de otimizar imagens para que o tamanho em bytes das imagens fosse o mínimo possível. Eu acho que as imagens podem ter uma visão rápida da falta de diferença no código gerado. Venho testando o código, gerando documentação real do que ele alegou e não apenas teorizando sobre o problema. mas nem todos vêem o mundo da mesma maneira. Lamento muitíssimo que minha tentativa de fornecer mais informações à comunidade o tenha incomodado tanto.
RTOSkit
11
Tenho certeza de que um tipo de retorno não especificado é sempre int, que geralmente é do tamanho do registro de trabalho ou de 16 bits, o que for menor.
Supercat
@supercat +1 Dedução perfeita, você está certo! Isso é exatamente o que eu discuto acima, um compilador projetado por padrão para uma arquitetura especificada, sempre reflete o tamanho do registro de trabalho da CPU / MPU em questão. Portanto, em um ambiente incorporado, existem várias estratégias de re-digitação em um SO em tempo real, em uma camada de compilador (stdint.h) ou em uma camada de portabilidade, que torna portátil o mesmo SO em muitas outras arquiteturas e obtido pelo alinhamento dos tipos específicos de CPU / MPU (int, long, long long, etc) com os tipos genéricos de sistema u8, u16, u32.
RTOSkit
@supercat Sem os tipos de controle oferecidos por um sistema operacional ou por um compilador específico, você precisa ter um pouco de atenção no tempo de desenvolvimento e faz com que todas as "atribuições não digitadas" estejam alinhadas com o design do aplicativo, antes de ter a surpresa de encontrar um " 16bit int "e não um" 32bit int ".
RTOSkit
@RTOSkit: sua resposta sugere que o tipo padrão em um processador de 8 bits será um Int8. Lembro-me de alguns compiladores C-ish para a arquitetura da marca PICmicro, onde esse era o caso, mas não acho que nada remotamente semelhante a um padrão C tenha permitido um inttipo que não fosse capaz de manter todos os valores no intervalo - 32767 a +32767 (a nota -32768 não é necessária) e também é capaz de manter todos os charvalores (o que significa que se charfor de 16 bits, ele deve ser assinado ou intdeve ser maior).
Supercat
5

Com relação ao tipo de parâmetro, já existem respostas corretas aqui, mas se você quiser ouvi-lo do compilador, tente adicionar alguns sinalizadores (os sinalizadores são quase sempre uma boa ideia).

compilando seu programa usando gcc foo.c -Wextraeu recebo:

foo.c: In function func’:
foo.c:5:5: warning: type of param defaults to int [-Wmissing-parameter-type]

estranhamente -Wextranão pega isso por clang(não reconhece -Wmissing-parameter-typepor algum motivo, talvez pelos históricos mencionados acima), mas -pedanticfaz:

foo.c:5:10: warning: parameter 'param' was not declared, 
defaulting to type 'int' [-pedantic]
int func(param)
         ^
1 warning generated.

E, para a questão do protótipo, como dito novamente acima, int func()refere-se a parâmetros arbitrários, a menos que você o defina de maneira exclusiva, o int func(void)que forneceria os erros conforme o esperado:

foo.c: In function func’:
foo.c:6:1: error: number of arguments doesnt match prototype
foo.c:3:5: error: prototype declaration
foo.c: In function main’:
foo.c:12:5: error: too many arguments to function func
foo.c:5:5: note: declared here

ou clangcomo:

foo.c:5:5: error: conflicting types for 'func'
int func(param)
    ^
foo.c:3:5: note: previous declaration is here
int func(void);
    ^
foo.c:12:20: error: too many arguments to function call, expected 0, have 1
    int bla = func(10);
              ~~~~ ^~
foo.c:3:1: note: 'func' declared here
int func(void);
^
2 errors generated.
Nenhum
fonte
3

Se a declaração da função não tiver parâmetros, isto é, vazia, estará recebendo um número não especificado de argumentos. Se você quiser fazê-lo, não use argumentos, altere-o para:

int func(void);
PP
fonte
11
"Se o protótipo de função não tiver parâmetros" Isso não é um protótipo de função, apenas uma declaração de função.
Effeffe
@effeffe corrigiu isso. Eu não sabia que esta questão iria receber muita atenção;)
PP
3
Não é "protótipo de função" apenas uma expressão alternativa (possivelmente antiquada) para "declaração de função"?
Giorgio
@Giorgio IMO, ambos estão corretos. "protótipo de função" deve corresponder à "definição de função". Eu provavelmente acho que effeffe significava declaração na pergunta e o que eu quis dizer está na minha resposta.
PP
@Giorgio no, a declaração de função é feita pelo valor do tipo de retorno, o identificador e uma lista de parâmetros opcional ; protótipos de função são declaração de função com uma lista de parâmetros.
Effeffe
0

É por isso que normalmente aconselho as pessoas a compilarem seu código:

cc -Wmissing-variable-declarations -Wstrict-variable-declarations -Wold-style-definition

Essas bandeiras reforçam algumas coisas:

  • - Declarações de variáveis ​​não permitidas: é impossível declarar uma função não estática sem primeiro obter um protótipo. Isso aumenta a probabilidade de um protótipo em um arquivo de cabeçalho corresponder à definição real. Como alternativa, aplica que você adicione a palavra-chave estática a funções que não precisam estar visíveis publicamente.
  • -Wstrict-variable-declarações: O protótipo deve listar corretamente os argumentos.
  • -Wold-style-definition: A própria definição de função também deve listar corretamente os argumentos.

Esses sinalizadores também são usados ​​por padrão em muitos projetos de código aberto. Por exemplo, o FreeBSD tem esses sinalizadores ativados ao criar com WARNS = 6 em seu Makefile.

Ed Schouten
fonte