Esta questão vem de uma tentativa de entender a "filosofia C" e não de nenhum "problema real".
Suponha que em um programa C eu use sin(x):
#include <stdio.h>
int main() {
char s[20] = "aha";
printf("%f", sin(s));
return 0;
}
Cometi dois erros deliberadamente:
- Não
#include
editei math.h (nem forneci nenhuma declaração para pecado). - Eu fiz
s
um tipochar *
(apenas algo que não pode ser convertido significativamente para umdouble
).
Eu compilo usando GCC com o -lm
sinalizador.
Como esperado, recebo um aviso e um erro.
Claro, há o aviso de declaração implícita de função blá blá blá:
bad.c: In function ‘main’:
bad.c:5:15: warning: implicit declaration of function ‘sin’ [-Wimplicit-function-declaration]
5 | printf("%f", sin(s));
| ^~~
bad.c:5:16: warning: incompatible implicit declaration of built-in function ‘sin’
bad.c:2:1: note: include ‘<math.h>’ or provide a declaration of ‘sin’
1 | #include <stdio.h>
+++ |+#include <math.h>
Uma pequena pesquisa parece indicar que se uma função não for declarada previamente, então C assume uma "declaração padrão" (a chamada "declaração implícita") que é como int undeclaredfunction(void)
.
Mas também recebo o seguinte erro:
2 |
bad.c:5:19: error: incompatible type for argument 1 of ‘sin’
5 | printf("%f", sin(s));
| ^
| |
| char *
bad.c:5:20: note: expected ‘double’ but argument is of type ‘char *’
Isso mostra que a "declaração implícita" espera um argumento do tipo double
.
- Parece que C mantém uma lista de "declarações implícitas" corretas para "funções padrão". Onde posso encontrar a lista abrangente de tais declarações implícitas corretas para um determinado compilador (digamos
gcc
ouclang
)? - Se C já tem as declarações corretas das funções matemáticas padrão incorporadas, então por que ele espera que o programador as tenha
#include<math.h>
? Só por tradição? Ou por limpeza?
A(s) resposta(s) que procuro são sobre as regras exatas usadas pelo compilador, e não sobre comentários subjetivos sobre "boas práticas de programação".
Não, não mostra isso. A razão pela qual o compilador esperava um
double
argumento não é porque havia uma declaração implícita. É porque o nomesin
é reservado.C 2024 7.1.3 diz:
Reservar o nome para uso pela biblioteca padrão C significa que, se um programa o usa de outra forma que não a reservada, o comportamento não é definido pelo padrão C. Ele não diz especificamente que uma implementação C deve tratar o nome como pré-declarado e conhecido pelo compilador, mas permite isso. Neste caso, o GCC sabia sobre o nome e emitiu um erro com base nisso. Ou seja, ele não usou uma declaração implícita genérica; ele usou seu próprio conhecimento do que a
sin
rotina deveria ser em particular.Em C 1990, um identificador não declarado usado para uma chamada de função seria implicitamente declarado como , conforme C 1990 6.3.2.2. Isso foi removido em C 1999. Alguns compiladores mantiveram o suporte para ele, particularmente em modos que não estão em conformidade com o padrão C atual.
extern int identifier();
O
()
naquela declaração significava que os tipos de parâmetros não foram especificados. (Isso mudou no C 2024; agora significaria que não há parâmetros.)O padrão C 2024 especifica identificadores reservados e formas de identificadores reservados principalmente na cláusula 7, com algumas menções em outros lugares. Por exemplo,
while
é reservado porque é uma palavra-chave, não porque é uma rotina de biblioteca. Alguns identificadores reservados são especificados como padrões, não como strings únicas específicas. Por exemplo, C 2024 6.4.3.1 diz que todos os identificadores que começam com dois sublinhados ou um sublinhado e uma letra maiúscula são reservados para qualquer uso.O GCC suporta muitas funções internas que começam com
__builtin
, e elas estão documentadas aqui . No entanto, essa não é uma lista completa de todos os identificadores reservados que ele usa. O GCC e o Clang também predefinem certas macros. O Clang documenta essas aqui . Não acho que haja um único lugar onde você encontrará todos os identificadores predefinidos (ou especiais) que o GCC e o Clang usam. Compiladores, bibliotecas e cabeçalhos de sistema podem usar identificadores reservados para propósitos internos sem documentá-los.Quando uma pessoa está escrevendo código para o kernel do sistema operacional e quer chamar sua rotina para escrever no log do sistema, ela quer
log
vincular a essa rotina, e ter sua declaração, e não ser alog
rotina matemática declarada em<math.h>
. As rotinas da biblioteca padrão foram agrupadas em subconjuntos para reduzir colisões de nomes para pessoas trabalhando em domínios diferentes. Ao compilar para um ambiente independente (como código do sistema operacional) em vez de um ambiente hospedado (os programas comuns com os quais você está mais familiarizado), os identificadores reservados são reduzidos.Além disso, embora
sin
seja reservado para uso com vinculação externa, um programador pode definir sua própriasin
rotina com vinculação interna (declarada comstatic
), e essa declaração pode ser diferente da rotina da biblioteca matemática.O padrão C faz pouca ou nenhuma menção a funções integradas, no entanto, algumas implementações C têm um conjunto de funções integradas que são "pré-declaradas", por assim dizer.
Para o GCC em particular, a página a seguir lista as funções integradas:
7 Funções integradas fornecidas pelo GCC
E esta página em particular lista as funções da biblioteca C que são incorporadas:
7.1 Builtins para funções de biblioteca C
A
sin
função está nesta lista, portanto a declaração correta deladouble sin(double)
existe como algo interno.Se esse mesmo código for compilado sob MSVC, ele não gera um erro. A lista de funções internas pode ser encontrada aqui:
Intrínsecos do compilador
E aparentemente
sin
não está entre sua lista de funções internas, então ele usa a declaração implícita deint sin()
(note que antes do C23 isso não é o mesmo queint sin(void)
, pois o primeiro tem um número não especificado de argumentos enquanto o último tem zero). Executar o programa subsequentemente acionaria um comportamento indefinido , pois asin
função está sendo chamada por meio de um ponteiro de função incompatível.Primeiro de tudo, declarações implícitas são obsoletas. Uma coisa do passado. Nunca confie nelas.
Uma declaração implícita é feita quando você usa a função pela primeira vez (sem uma declaração explícita anterior). No entanto, você a usou e ela se tornará a declaração implícita.
int
como um valor de retorno aparentemente parecia um bom palpite no momento em que declarações implícitas foram aceitas, então a declaração implícita significa que a função deve retornarint
.Você está cavando um túmulo. Não há uma lista de declarações corretas. Compiladores modernos sintonizados com os padrões modernos de C recusarão programas que dependem de declarações implícitas. Historicamente, o compilador errou muitas vezes e fez uma bagunça no seu programa final, então eles removeram declarações implícitas da linguagem.
Não, isso está errado. Costumava ser assim nos anos 1990, mas foi um grande erro de design da linguagem, razão pela qual a declaração implícita/int implícito foi removida da linguagem no ano de 1999.
Primeiro de tudo, o compilador gcc pode, infelizmente, como um "recurso" usar certas funções/macros padrão, mesmo quando você esqueceu de incluir o cabeçalho relevante. Isso introduz um potencial para que bugs permaneçam sem serem detectados sempre que usar o gcc, já que ele "apenas" dá um aviso, até a versão mais recente 14.x que dá um erro. (Eu acho que isso tem a ver com o C23 tornando certas declarações de estilo K&R inválidas.)
A capacidade de usar versões "embutidas" de funções de lib padrão pode estar relacionada a se a lib padrão implementa a função como uma macro do tipo função ou não. Quando eu altero seu código para o C ainda válido
(sin)(s)
, o compilador perde a capacidade de expandir isso como uma macro do tipo função e, de repente, ele não sabe nada sobresin
(porque não incluímos math.h).Não, isso é só uma coisa do gcc.
O compilador pode não seguir o padrão C. Na verdade, o gcc não vem pronto para uso. Se você quiser que ele se comporte como um compilador C padrão, você tem que dizer para ele compilar com, por exemplo: