Comportamento indefinido com declarações de função obsoletas no ANSI C


O padrão ANSI C define o conceito de um protótipo de função , que é um subconjunto de uma declaração de função que indica os tipos de parâmetros de entrada. Protótipos foram introduzidos para eliminar as desvantagens das declarações de funções comuns.


Portanto, a obrigatoriedade de especificar uma lista de tipos de parâmetros entre parênteses de um protótipo de função é obrigatória; caso contrário, essa expressão será reconhecida pelo compilador como uma declaração de função obsoleta, o que pode levar a situações ambíguas descritas neste artigo.


Protótipos desatualizados


Uma declaração de função introduz o tipo de retorno da função e seu identificador no escopo especificado. Observe que nem todas as declarações de funções podem ser consideradas protótipos, mas apenas aquelas que possuem uma lista de tipos de parâmetros de entrada.


Portanto, a primeira expressão do código abaixo é uma declaração, mas não um protótipo de função. A expressão a seguir pode legitimamente ser considerada um protótipo, pois especifica os tipos de seus parâmetros:


/* #1 (   "foo") */ void foo(); /* #2 (  "bar") */ void bar(int count, const char *word); 

Definições obsoletas


Vamos direto para 1972 (o ano em que a linguagem C foi lançada) e lembre-se de como os programadores da época definiram suas funções. Deixe-me lembrá-lo de que a definição de uma função conecta sua assinatura ao bloco executável correspondente (corpo). Este código demonstra a definição da função add no estilo K&R:


 void add(right, left, result) int right; int left; int *result; { *result = right + left; } 

Como você deve ter notado, nesta entrada, os parênteses identificam a função, mas não contêm nenhum tipo de parâmetro de entrada. As declarações da função “clássica” descritas na seção anterior têm a mesma propriedade.


Situações ambíguas


É possível que, se a nova sintaxe de protótipos e definições de funções introduzidas pelo padrão ANSI C não for observada, situações ambíguas difíceis possam surgir. Considere um exemplo:


 #include <stdio.h> #include <stdint.h> #include <inttypes.h> #include <limits.h> /*    "print_number" */ void print_number(); int main(void) { /*  */ print_number((double)13.359); print_number((double)9238.46436); print_number((double)18437); /*    */ print_number(UINT64_MAX); print_number("First", "Second", "Third"); print_number(NULL, "Breakfast", &print_number); } void print_number(double number) { printf(" : [%f]\n", number); } 

Vamos analisar este programa. A print_number função print_number correta print_number declarada sem especificar uma lista de tipos de parâmetros, para que você possa chamar essa função com qualquer argumento. O programa compilou sem erros e imprimiu o seguinte resultado:


 $ gcc illegal.c -o illegal -Wall $ ./illegal  : [13.359000]  : [9238.464360]  : [18437.000000]  : [0.000000]  : [0.000000]  : [0.000000] 

Observe também que, mesmo com o sinalizador -Wall , o compilador -Wall gcc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0 não gerou nenhum aviso (mas seria altamente desejável).


A correção deste programa não será difícil, basta adicionar o double number entre parênteses da declaração da função print_number na sétima linha, após o qual qualquer compilador seguindo o padrão indicará erros na função main() :


 $ gcc -Wall illegal.c -o illegal illegal.c: In function 'main': illegal.c:17:22: error: incompatible type for argument 1 of 'print_number' print_number("First", "Second", "Third"); ^~~~~~~ illegal.c:7:6: note: expected 'double' but argument is of type 'char *' void print_number(double number); ^~~~~~~~~~~~ illegal.c:17:9: error: too many arguments to function 'print_number' print_number("First", "Second", "Third"); ^~~~~~~~~~~~ illegal.c:7:6: note: declared here void print_number(double number); ^~~~~~~~~~~~ illegal.c:18:22: error: incompatible type for argument 1 of 'print_number' print_number(NULL, "Breakfast", &print_number); ^~~~ illegal.c:7:6: note: expected 'double' but argument is of type 'void *' void print_number(double number); ^~~~~~~~~~~~ illegal.c:18:9: error: too many arguments to function 'print_number' print_number(NULL, "Breakfast", &print_number); ^~~~~~~~~~~~ illegal.c:7:6: note: declared here void print_number(double number); ^~~~~~~~~~~~ 

Funções sem parâmetros


Também observo que especificar a palavra-chave void entre parênteses de protótipos e definições de funções que não aceitam parâmetros é altamente desejável (mas não necessário). Se essa regra não for observada, o compilador não poderá verificar a correspondência dos tipos dos argumentos passados ​​quando a função for chamada com tipos válidos da definição.


 #include <stdio.h> /*    "do_something" */ void do_something(); int main(void) { /*  "do_something"       */ do_something(NULL, "Papa Johns", 2842, 1484.3355); } void do_something() { puts("I am doing something interesting right now!"); } 

O código acima deve ser corrigido inserindo a palavra-chave void na definição e declaração da função do_something() , caso contrário, este programa será compilado sem erros. Neste exemplo, a função main() também é definida com o token void nos parâmetros, embora isso não seja necessário.


Conclusão


A redação deste artigo foi inspirada no livro de Stephen Prat "Linguagem de programação C. Palestras e exercícios. Sexta edição", e especificamente na seção "Funções com argumentos" do quinto capítulo.

Source: https://habr.com/ru/post/pt456682/


All Articles