implicit declaration of function : causes et correction
Depuis GCC 14, appeler une fonction non déclarée est une erreur. Include oublié, prototype manquant, faute de frappe : la cause et le correctif de chaque cas.
Le même fichier compilait l'an dernier, ou sur la machine d'un camarade, avec au pire un warning jaune qu'on laissait défiler. Ton GCC le refuse maintenant net : error: implicit declaration of function. GCC a simplement cessé de deviner à ta place.
En bref
implicit declaration of function 'f' signifie que tu appelles f à un endroit où le compilateur n'a encore vu aucune déclaration de cette fonction. Depuis GCC 14, c'est une erreur par défaut en C99 et après, et donc avec GCC 15. Trois causes couvrent presque tous les cas : l'en-tête qui déclare la fonction n'est pas inclus (<stdio.h> pour printf, <string.h> pour strdup, <unistd.h> pour sleep ou fork), la fonction est définie plus bas que main sans prototype au-dessus, ou son nom est mal écrit. On corrige la déclaration, pas les options de compilation.
Que veut dire « implicit declaration of function » ?
Le compilateur C lit ton fichier de haut en bas. Quand il rencontre un appel, il doit déjà connaître la fonction : son type de retour et le type de chacun de ses paramètres. Cette description s'appelle une déclaration, et quand elle précise les paramètres, un prototype. Sans elle, il ne sait ni comment passer les arguments ni quoi faire de la valeur renvoyée.
Le cas le plus court qui soit, un printf sans #include :
int main(void){ printf("Bonjour\n"); return 0;}$ gcc -Wall -Wextra hello.c -o hellohello.c: In function 'main':hello.c:3:5: error: implicit declaration of function 'printf' [-Wimplicit-function-declaration] 3 | printf("Bonjour\n"); | ^~~~~~hello.c:1:1: note: include '<stdio.h>' or provide a declaration of 'printf' +++ |+#include <stdio.h> 1 | int main(void)hello.c:3:5: warning: incompatible implicit declaration of built-in function 'printf' [-Wbuiltin-declaration-mismatch] 3 | printf("Bonjour\n"); | ^~~~~~hello.c:3:5: note: include '<stdio.h>' or provide a declaration of 'printf'La ligne utile est la note : GCC propose même la ligne à ajouter, préfixée par +++. Le warning qui suit, incompatible implicit declaration of built-in function, vient de ce que GCC connaît printf de naissance : il sait que la déclaration qu'il aurait inventée ne correspond pas à la vraie. Ajoute #include <stdio.h> en tête de fichier et les quatre messages disparaissent ensemble.
Pourquoi ce code compilait avant ?
Parce que la norme a changé en 1999 et que GCC a mis vingt-cinq ans à en tirer toutes les conséquences. En C89, appeler une fonction inconnue était permis : le compilateur faisait comme si tu avais écrit extern int f();, une fonction qui renvoie un int et dont on ne vérifie pas les arguments. La norme C99 a supprimé cette déclaration implicite, mais GCC a continué à l'accepter avec un simple warning jusqu'à GCC 13. GCC 14, sorti le 7 mai 2024, en a fait une erreur, et GCC 15 la garde.
| Mode ou version | Appel d'une fonction non déclarée |
|---|---|
-std=gnu89 | accepté, sans un mot si tu n'actives pas -Wall |
| GCC 5 à 13, modes C99 et suivants | accepté avec un warning |
| GCC 14 et 15 | refusé : error, aucun exécutable produit |
GCC 14 et 15 avec -fpermissive | accepté avec un warning |
L'ancien comportement existe donc toujours derrière une option. Avec -fpermissive, le même hello.c compile et s'exécute :
$ gcc -fpermissive hello.c -o hello && ./hellohello.c: In function 'main':hello.c:3:5: warning: implicit declaration of function 'printf' [-Wimplicit-function-declaration] 3 | printf("Bonjour\n"); | ^~~~~~hello.c:1:1: note: include '<stdio.h>' or provide a declaration of 'printf' +++ |+#include <stdio.h> 1 | int main(void)hello.c:3:5: warning: incompatible implicit declaration of built-in function 'printf' [-Wbuiltin-declaration-mismatch] 3 | printf("Bonjour\n"); | ^~~~~~hello.c:3:5: note: include '<stdio.h>' or provide a declaration of 'printf'BonjourIci ça tombe juste, parce que printf renvoie bel et bien un int. La section suivante montre pourquoi les responsables de GCC ont fini par fermer la porte.
Pourquoi l'appel implicite est dangereux
Une fonction déclarée implicitement est supposée renvoyer un int. Si elle renvoie autre chose, un pointeur par exemple, l'appelant ne récupère que la partie qui tient dans un int. Sur une machine 64 bits, un int fait 32 bits et un pointeur 64 : la moitié de l'adresse disparaît en route.
Voici deux fichiers. nom.c définit une fonction qui renvoie un pointeur, main.c l'appelle sans l'avoir déclarée :
#include <stdio.h>#include <stdlib.h>#include <string.h>
char *creer_nom(void){ char *nom = malloc(1 << 20);
strcpy(nom, "grademe"); printf("creer_nom renvoie %p\n", (void *)nom); return nom;}#include <stdio.h>
int main(void){ char *nom = creer_nom();
printf("main reçoit %p\n", (void *)nom); printf("%s\n", nom); return 0;}GCC 15 refuse de compiler, avec deux erreurs : la déclaration implicite, puis la conversion d'un int en pointeur que cette déclaration entraîne. En forçant l'ancien comportement avec -fpermissive, on voit ce qu'elles empêchent :
$ gcc -Wall -Wextra main.c nom.c -o nommain.c: In function 'main':main.c:5:17: error: implicit declaration of function 'creer_nom' [-Wimplicit-function-declaration] 5 | char *nom = creer_nom(); | ^~~~~~~~~main.c:5:17: error: initialization of 'char *' from 'int' makes pointer from integer without a cast [-Wint-conversion]$ gcc -fpermissive main.c nom.c -o nommain.c: In function 'main':main.c:5:17: warning: implicit declaration of function 'creer_nom' [-Wimplicit-function-declaration] 5 | char *nom = creer_nom(); | ^~~~~~~~~main.c:5:17: warning: initialization of 'char *' from 'int' makes pointer from integer without a cast [-Wint-conversion]$ ./nomcreer_nom renvoie 0xffff9809f010main reçoit 0xffffffff9809f010Segmentation faultCompare les deux adresses. Les 32 bits du bas (9809f010) ont survécu, ceux du haut ont été remplacés par des f : main a reçu un int négatif, qu'il a étendu en pointeur. Cette adresse ne désigne rien, et le premier accès plante. Le crash survient dans le dernier printf, mais la faute est en amont : main.c ne déclare nulle part creer_nom. C'est exactement le genre d'erreur que GCC 14 a choisi de bloquer à la compilation.
Les arguments ne sont pas mieux traités. Une fonction qui attend deux double, appelée avant sa définition, recevrait deux int que personne ne convertit :
#include <stdio.h>
int main(void){ printf("%.1f\n", moyenne(12, 15)); return 0;}
double moyenne(double a, double b){ return (a + b) / 2;}GCC constate le conflit entre ce qu'il avait supposé à la ligne 5 et ce qu'il découvre à la ligne 9 :
$ gcc -Wall -Wextra moyenne2.c -o moyenne2...moyenne2.c:9:8: error: conflicting types for 'moyenne'; have 'double(double, double)' 9 | double moyenne(double a, double b) | ^~~~~~~moyenne2.c:5:22: note: previous implicit declaration of 'moyenne' with type 'int()' 5 | printf("%.1f\n", moyenne(12, 15)); | ^~~~~~~with type 'int()' : voilà la déclaration que le compilateur avait inventée, en toutes lettres.
Cause 1 : l'en-tête n'est pas inclus
C'est le cas de printf plus haut. Chaque fonction de la bibliothèque C est déclarée dans un en-tête précis, et l'oubli ne se voit qu'à la compilation.
$ gcc -Wall -Wextra copie.c -o copiecopie.c: In function 'main':copie.c:5:19: error: implicit declaration of function 'strdup' [-Wimplicit-function-declaration] 5 | char *copie = strdup("grademe"); | ^~~~~~copie.c:5:19: warning: incompatible implicit declaration of built-in function 'strdup' [-Wbuiltin-declaration-mismatch]$ gcc -Wall -Wextra pause.c -o pausepause.c: In function 'main':pause.c:6:5: error: implicit declaration of function 'sleep' [-Wimplicit-function-declaration] 6 | sleep(1); | ^~~~~Cette fois, GCC ne suggère aucun en-tête. Pour le trouver, la page de manuel donne la réponse dans sa section SYNOPSIS. Le chiffre est la section du manuel : 3 pour les fonctions de la bibliothèque, 2 pour les appels système (la fiche man(1) détaille le découpage).
$ man 3 strdup | head -20strdup(3) Library Functions Manual strdup(3)
NAME strdup, strndup, strdupa, strndupa - duplicate a string
LIBRARY Standard C library (libc, -lc)
SYNOPSIS #include <string.h>
char *strdup(const char *s);
char *strndup(const char s[.n], size_t n); char *strdupa(const char *s); char *strndupa(const char s[.n], size_t n);
Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
strdup():Les en-têtes des fonctions qu'on oublie le plus souvent, relevés dans les pages de manuel :
| Fonction | En-tête | Page à ouvrir |
|---|---|---|
printf, scanf, fgets | <stdio.h> | man 3 printf |
malloc, free, atoi, exit | <stdlib.h> | man 3 malloc |
strlen, strdup, strcmp, memcpy | <string.h> | man 3 strlen |
write, fork, sleep | <unistd.h> | man 2 write |
waitpid | <sys/wait.h> | man 2 waitpid |
isalpha, toupper | <ctype.h> | man 3 isalpha |
sqrt, pow | <math.h>, plus -lm à l'édition de liens | man 3 sqrt |
La dernière ligne cache un second piège : une fois <math.h> inclus, la compilation passe, mais l'édition de liens peut encore échouer sur undefined reference to `sqrt'. C'est une autre étape et une autre erreur, expliquées dans undefined reference to en C.
Cause 2 : la fonction est définie après main
Tu as écrit ta fonction, mais en dessous de main. Au moment de l'appel, le compilateur ne l'a pas encore lue.
#include <stdio.h>
int main(void){ printf("%d\n", moyenne(12, 15)); return 0;}
int moyenne(int a, int b){ return (a + b) / 2;}$ gcc -Wall -Wextra moyenne.c -o moyennemoyenne.c: In function 'main':moyenne.c:5:20: error: implicit declaration of function 'moyenne' [-Wimplicit-function-declaration] 5 | printf("%d\n", moyenne(12, 15)); | ^~~~~~~Deux corrections sont valables : remonter la définition au-dessus de main, ou laisser la définition où elle est et annoncer la fonction en haut du fichier avec son prototype, c'est-à-dire la première ligne de la définition suivie d'un point-virgule.
#include <stdio.h>
int main(void){ printf("%d\n", moyenne(12, 15)); return 0;}#include <stdio.h>
int moyenne(int a, int b);
int main(void){ printf("%d\n", moyenne(12, 15)); return 0;}$ gcc -Wall -Wextra moyenne_ok.c -o moyenne && ./moyenne13Dès que le programme tient sur plusieurs fichiers, les prototypes partent dans un en-tête .h que chaque fichier inclut. C'est le même mécanisme que <stdio.h>, à ton échelle.
fix-the-prototype
report.c ne compile pas : main appelle print_report, définie plus bas. Lis le diagnostic, puis répare le fichier avec un prototype ou en déplaçant la définition, sans changer un octet des trois lignes affichées.
Cause 3 : une faute de frappe dans le nom
Le compilateur ne sait pas que prinft est une coquille : pour lui, c'est une fonction inconnue de plus. GCC compare toutefois le nom avec ceux qu'il connaît et propose le plus proche.
$ gcc -Wall -Wextra typo.c -o typotypo.c: In function 'main':typo.c:5:5: error: implicit declaration of function 'prinft'; did you mean 'printf'? [-Wimplicit-function-declaration] 5 | prinft("Bonjour\n"); | ^~~~~~ | printfQuand le did you mean apparaît, lis-le en premier. Avec -fpermissive, qui reproduit le comportement des anciennes versions, la coquille passe la compilation et ne se révèle qu'à l'édition de liens, avec un message bien moins parlant :
$ gcc -fpermissive typo.c -o typotypo.c: In function 'main':typo.c:5:5: warning: implicit declaration of function 'prinft'; did you mean 'printf'? [-Wimplicit-function-declaration] 5 | prinft("Bonjour\n"); | ^~~~~~ | printf/usr/bin/ld: /tmp/ccTpuBQh.o: in function `main':typo.c:(.text+0x10): undefined reference to `prinft'collect2: error: ld returned 1 exit statusQuestions fréquentes
J'ai inclus <string.h> et j'ai quand même l'erreur sur strdup, pourquoi ?
Tu compiles sans doute avec -std=c99 ou -std=c17. Dans ces modes stricts, la glibc ne déclare que ce que la norme C prévoit, et strdup est une fonction POSIX qui n'est entrée dans la norme qu'avec C23. GCC répond alors implicit declaration of function 'strdup'; did you mean 'strcmp'? malgré l'include. Ajoute #define _POSIX_C_SOURCE 200809L avant tout #include, comme l'indique la section « Feature Test Macro Requirements » de man 3 strdup, ou compile en -std=gnu17 ou en C23.
Que veut dire « incompatible implicit declaration of built-in function » ?
GCC connaît d'avance la signature de nombreuses fonctions standard, comme printf(3), strdup(3) ou malloc. Quand tu en appelles une sans l'en-tête, il remarque que la déclaration int f() qu'il aurait supposée ne correspond pas à la vraie. Le remède est le même : inclure l'en-tête.
Pourquoi « implicit declaration of function 'itoa' » alors que j'ai inclus <stdlib.h> ?
itoa ne fait partie ni de la norme C ni de POSIX, et la glibc ne la fournit pas : man 3 itoa répond No manual entry. Pour convertir un entier en texte, utilise snprintf(texte, sizeof texte, "%d", n), ou écris ta propre version comme dans int_to_str.
Faut-il écrire un prototype pour chaque fonction ?
Oui pour chaque fonction appelée avant sa définition ou définie dans un autre fichier. Une fonction définie plus haut dans le même fichier sert de déclaration à elle seule. En pratique, on met les prototypes des fonctions partagées dans un .h, et on garde l'ordre « définition avant appel » pour les petites fonctions internes, déclarées static.
Sources
GCC 14 : Porting to GCC 14, erreurs par défaut dont implicit-function-declaration
GCC 14.1 : -Wimplicit-function-declaration, « enabled by default, as an error, in C99 and later »
GCC 13.2 : -Wimplicit-function-declaration, encore un simple warning
cppreference : déclaration implicite en
extern int f();jusqu'à C99
