J'utilise le code ci-dessous:
char dest[5];
char src[5] = "test";
printf("String: %s\n", do_something(dest, src));
char *do_something(char *dest, const char *src)
{
return dest;
}
L'implémentation de do_something
n'est pas important ici. Lorsque j'essaie de compiler ce qui précède, j'ai deux exceptions:
erreur: types en conflit pour 'do_something' (lors de l'appel printf)
erreur: la déclaration implicite précédente de 'quelque chose' était ici (à la ligne du prototype)
Pourquoi?
Vous essayez d'appeler do_something avant de le déclarer. Vous devez ajouter un prototype de fonction avant votre ligne printf:
char* do_something(char*, const char*);
Ou vous devez déplacer la définition de fonction au-dessus de la ligne printf. Vous ne pouvez pas utiliser une fonction avant sa déclaration.
Dans le langage C "classique" (C89/90), lorsque vous appelez une fonction non déclarée, C suppose qu’elle retourne un int
et tente également de dériver les types de ses paramètres des types des arguments réels (no, il ne suppose pas qu’il n’a pas de paramètres, comme l’a suggéré quelqu'un auparavant).
Dans votre exemple spécifique, le compilateur se pencherait sur do_something(dest, src)
call et en déduirait implicitement une déclaration pour do_something
. Ce dernier ressemblerait à ce qui suit
int do_something(char *, char *)
Cependant, plus tard dans le code, vous déclarez explicitement do_something
comme
char *do_something(char *, const char *)
Comme vous pouvez le constater, ces déclarations sont différentes les unes des autres. C'est ce que le compilateur n'aime pas.
Vous ne l'avez pas déclaré avant de l'utiliser.
Vous avez besoin de quelque chose comme
char *do_something(char *, const char *);
avant le printf.
Exemple:
#include <stdio.h>
char *do_something(char *, const char *);
char dest[5];
char src[5] = "test";
int main ()
{
printf("String: %s\n", do_something(dest, src));
return 0;
}
char *do_something(char *dest, const char *src)
{
return dest;
}
Alternativement, vous pouvez mettre l'ensemble do_something
fonction avant l'impression.
Vous devez déclarer la fonction avant de l'utiliser. Si le nom de la fonction apparaît avant sa déclaration, le compilateur C suivra certaines règles et effectuera la déclaration lui-même. Si c'est faux, vous aurez cette erreur.
Vous avez deux options: (1) définissez-la avant de l'utiliser, ou (2) utilisez la déclaration anticipée sans implémentation. Par exemple:
char *do_something(char *dest, const char *src);
Notez le point-virgule à la fin.
Fiche d'information sur la déclaration de fonction A C
En C, les déclarations de fonction ne fonctionnent pas comme dans d'autres langages: le compilateur C lui-même ne cherche pas en arrière dans le fichier pour trouver la déclaration de la fonction à l'endroit où vous l'appelez, et n'analyse pas le fichier. Plusieurs fois pour comprendre les relations: le compilateur ne fait que balayer en avant dans le fichier exactement une fois , de haut en bas. La connexion des appels de fonction aux déclarations de fonction fait partie du travail de l'éditeur de liens et n'est effectuée qu'après le fichier est compilé en instructions d'assemblage brutes.
Cela signifie que, lorsque le compilateur parcourt le fichier, la première fois que le compilateur rencontre le nom d'une fonction, il faut que l'une des deux choses soit le cas: soit voir la déclaration de la fonction elle-même, auquel cas le compilateur sait exactement ce que la fonction est et quels types elle prend comme arguments et quels types elle renvoie - ou c'est un appel à la fonction, et le compilateur doit deviner comment la fonction sera éventuellement déclarée.
(Il existe une troisième option, où le nom est utilisé dans un prototype de fonction, mais nous l'ignorerons pour l'instant, car si vous rencontrez ce problème en premier lieu, vous n'utilisez probablement pas de prototypes.)
Leçon d'histoire
Dans les premiers jours de C, le fait que le compilateur devine les types n'était pas vraiment un problème: tous les types étaient plus ou moins les mêmes - à peu près tout était soit un int, soit un pointeur, et ils l'étaient. la même taille. (En fait, dans B, le langage qui a précédé C, il n'y avait aucun type; tout était simplement un int ou un pointeur et son type était déterminé uniquement par la façon dont vous l'aviez utilisé!). Ainsi, le compilateur pouvait deviner en toute sécurité le comportement de fonction basée uniquement sur le nombre de paramètres transmis: si vous transmettez deux paramètres, le compilateur place deux éléments dans la pile d'appels, et l'appelé doit avoir deux arguments déclarés, et tous ces éléments sont alignés. Si vous ne transmettiez qu'un paramètre mais que la fonction en attendait deux, le travail serait encore trié et le second argument serait simplement ignoré/garbage. Si vous transmettiez trois paramètres et que la fonction en attendait deux, le travail serait encore trié et le troisième paramètre serait ignoré et modifié par les variables locales de la fonction. (Certains anciens codes C s’attendent toujours à ce que ces règles d’arguments incompatibles fonctionnent également.)
Cependant, laisser le compilateur vous laisser passer n'importe quoi n'est pas vraiment un bon moyen de concevoir un langage de programmation. Cela a bien fonctionné au début, car les premiers programmeurs C étaient principalement des sorciers, et ils savaient qu'ils ne devaient pas passer du type incorrect aux fonctions, et même s'ils se trompaient, il y avait toujours des outils comme lint
qui Vous pourriez procéder à une double vérification approfondie de votre code C et vous avertir de telles choses.
Avancez jusqu'à aujourd'hui, et nous ne sommes pas tout à fait dans le même bateau. C a grandi, et beaucoup de personnes y programment des programmes qui ne sont pas des sorciers, et pour les accueillir (et pour tous ceux qui utilisent régulièrement lint
de toute façon), les compilateurs ont pris beaucoup de capacités qui faisaient auparavant partie de lint
- en particulier la partie où ils vérifient votre code pour s’assurer qu’il est conforme au type. Les premiers compilateurs C vous permettraient d'écrire int foo = "hello";
Et assigneraient allègrement le pointeur à l'entier, et il reviendrait à vous de vous assurer que vous ne faites rien de stupide. Les compilateurs C modernes se plaignent très fort lorsque vous vous trompez, et c'est une bonne chose.
Type Conflicts
Alors, qu'est-ce que tout cela a à voir avec la mystérieuse erreur de type en conflit sur la ligne de la déclaration de fonction? Comme je l'ai dit plus haut, les compilateurs C doivent toujours savoir ou deviner quel nom signifie la première fois qu'ils voient ce nom lorsqu'ils parcourent le fichier: Ils peuvent savoir ce que cela signifie si c'est une déclaration de fonction réelle lui-même (ou une fonction "prototype", plus à ce sujet prochainement), mais s'il ne s'agit que d'un appel à la fonction, ils doivent deviner . Et, malheureusement, la supposition est souvent fausse.
Lorsque le compilateur a vu votre appel à do_something()
, il a examiné la manière dont il a été appelé et a conclu que do_something()
serait finalement déclaré comme ceci:
int do_something(char arg1[], char arg2[])
{
...
}
Pourquoi a-t-il conclu cela? Parce que c'est comme ça que vous l'avez appelé ! (Certains compilateurs C peuvent en conclure que c’était int do_something(int arg1, int arg2)
ou simplement int do_something(...)
, les deux étant même plus éloignés de ce que vous voulez, mais le point important est que peu importe la façon dont le compilateur devine les types, il les devine différemment de ce que votre fonction actuelle utilise.)
Plus tard, au fur et à mesure que le compilateur parcourt le fichier, il voit votre déclaration réelle de char *do_something(char *, char *)
. Cette déclaration de fonction n'est même pas proche de la déclaration devinée par le compilateur, ce qui signifie que la ligne où le compilateur a compilé l'appel a été mal compilée et que le programme ne fonctionne tout simplement pas. Donc, il écrit à juste titre une erreur en vous disant que votre code ne va pas fonctionner comme il est écrit.
Vous vous demandez peut-être: "Pourquoi suppose-t-on que je retourne un int
?" Eh bien, il suppose ce type car il n’existe aucune information contraire: printf()
peut prendre n’importe quel type dans ses arguments variables, donc sans une meilleure réponse, int
est aussi bon à deviner que n'importe quel autre. (De nombreux premiers compilateurs C ont toujours supposé que int
pour chaque type non spécifié, et supposaient que vous vouliez dire ...
Pour les arguments de chaque fonction déclarée f()
- pas void
- C’est pourquoi de nombreuses normes de code modernes recommandent de toujours mettre void
dans les arguments s’il n’y en a pas réellement.)
Le correctif
Il existe deux correctifs courants pour l’erreur de déclaration de fonction.
La première solution, recommandée par de nombreuses autres réponses ici, consiste à placer un prototype dans le code source ci-dessus. l'endroit où la fonction est appelée en premier. Un prototype ressemble à la déclaration de la fonction, mais il comporte un point-virgule où le corps devrait être:
char *do_something(char *dest, const char *src);
En mettant le prototype en premier, le compilateur sait alors à quoi ressemblera la fonction, il n’a donc pas à le deviner. Par convention, les programmeurs placent souvent des prototypes en haut du fichier, juste en dessous des instructions #include
, Pour s'assurer qu'ils seront toujours définis avant toute utilisation potentielle de ceux-ci.
L’autre solution, qui apparaît également dans certains codes du monde réel, consiste simplement à réorganiser vos fonctions afin que leurs déclarations soient toujours avant tout ce qui appelle leur! Vous pouvez déplacer la totalité de la fonction char *do_something(char *dest, const char *src) { ... }
au-dessus du premier appel. Le compilateur sait alors exactement à quoi ressemble la fonction et ne doit pas le deviner.
En pratique, la plupart des gens utilisent des prototypes de fonctions, car vous pouvez également les prendre et les déplacer dans des fichiers d'en-tête (.h
) Afin que le code des autres fichiers .c
Puisse appeler ces fonctions. Mais l'une ou l'autre solution fonctionne et de nombreuses bases de code utilisent les deux.
C99 et C11
Il est utile de noter que les règles sont légèrement différentes dans les versions les plus récentes de la norme C. Dans les versions précédentes (C89 et K & R), le compilateur devinait réellement les types au moment de l'appel de fonction (et les compilateurs K & R-era ne même vous avertir si ils avaient tort). C99 et C11 exigent tous les deux que la déclaration de la fonction/prototype doit précéder le premier appel, sinon c'est une erreur. Mais de nombreux compilateurs C modernes - principalement pour la compatibilité ascendante avec le code précédent - ne préviendront que un prototype manquant et ne le considéreront pas comme une erreur.
C Commandement # 3:
K&R #3 Thou shalt always prototype your functions or else the C compiler will extract vengence.
Lorsque vous ne spécifiez pas de prototype pour la fonction avant de l’utiliser, C suppose qu’elle prend un nombre quelconque de paramètres et renvoie un int. Ainsi, lorsque vous essayez pour la première fois d'utiliser do_something, c'est le type de fonction que le compilateur recherche. Cela devrait générer un avertissement concernant une "déclaration de fonction implicite".
Ainsi, dans votre cas, lorsque vous déclarez la fonction ultérieurement, C n'autorise pas la surcharge de fonction, ce qui engendre une perte de contrôle, car vous avez déclaré deux fonctions avec des prototypes différents mais avec le même nom.
Réponse courte: déclarez la fonction avant d'essayer de l'utiliser.
Regarder à nouveau:
char dest[5];
char src[5] = "test";
printf("String: %s\n", do_something(dest, src));
Focus sur cette ligne:
printf("String: %s\n", do_something(dest, src));
Vous pouvez clairement voir que la fonction do_something n'est pas déclarée!
Si vous regardez un peu plus loin,
printf("String: %s\n", do_something(dest, src));
char *do_something(char *dest, const char *src)
{
return dest;
}
vous verrez que vous déclarez la fonction after vous l'utilisez.
Vous devrez modifier cette partie avec ce code:
char *do_something(char *dest, const char *src)
{
return dest;
}
printf("String: %s\n", do_something(dest, src));
À votre santé ;)
Cela se produit souvent lorsque vous modifiez une définition de fonction c sans oublier de mettre à jour la définition d'en-tête correspondante.
Assurez-vous que les types dans la déclaration de fonction sont déclarés en premier./* start of the header file */
.
.
.struct intr_frame{...}; //must be first!
.
.
.void kill (struct intr_frame *);
.
.
./* end of the header file */