Appeler une fonction depuis un autre fichier du même répertoire en C

J’apprends le C, mais j’ai une longue expérience des langages de programmation de niveau supérieur comme Java.

J’étais en train de lire sur les fichiers d’en-tête, donc je m’amusais avec eux. Cependant, j’ai remarqué que je pouvais appeler une fonction depuis un autre fichier sans l’inclure (c’est dans le même répertoire), comment est-ce possible?! Est-ce le fichier make, l’éditeur de liens qui est configuré de cette façon ou quoi?

Nous avons deux fichiers

main.c add.c 

main.c appelle la fonction add(int x,int y) de add add.c, mais j’ai compilé par erreur avant #including add.c et cela a fonctionné! Ce qui le rend plus déroutant est que quand i #include add.c, cela donne une erreur de définition multiple sur la fonction add

Il y a quelques choses différentes qui se passent ici. Je vais d’abord examiner comment fonctionne la compilation de base de plusieurs fichiers.

Si vous avez plusieurs fichiers, l’important est la différence entre la déclaration et la définition d’une fonction. La définition est probablement celle à laquelle vous êtes habitué lorsque vous définissez des fonctions: vous écrivez le contenu de la fonction, comme

 int square(int i) { return i*i; } 

La déclaration, par contre, vous permet de déclarer au compilateur que vous savez qu’une fonction existe, mais vous ne dites pas au compilateur de quoi il s’agit. Par exemple, vous pourriez écrire

 int square(int i); 

Et le compilateur s’attendrait à ce que la fonction “carré” soit définie ailleurs.

Maintenant, si vous voulez interopérer avec deux fichiers différents (par exemple, supposons que la fonction “square” soit définie dans add.c et que vous souhaitiez appeler square (10) dans main.c), vous devez faire à la fois une définition et une déclaration. Tout d’abord, vous définissez square dans add.c. Ensuite, vous le déclarez au début de main.c. Ceci permet au compilateur de savoir, lorsqu’il comstack main.c, qu’il existe une fonction “carré” définie ailleurs. Maintenant, vous devez comstackr main.c et add.c dans des fichiers object. Vous pouvez le faire en appelant

 gcc -c main.c gcc -c add.c 

Cela produira les fichiers main.o et add.o. Ils contiennent les fonctions compilées, mais ne sont pas tout à fait exécutables. La chose importante à comprendre ici est que main.o est “incomplet” dans un sens. Lors de la compilation de main.o, vous lui avez dit que la fonction “carré” existe, mais que la fonction “carré” n’est pas définie dans main.o. Ainsi, main.o a une sorte de “référence en suspens” à la fonction “carré”. Il ne sera compilé dans un programme complet que si vous le combinez avec un autre fichier .o (ou un fichier .so ou .a) contenant la définition de “carré”. Si vous essayez simplement de lier main.o à un programme, c’est-à-dire

 gcc -o executable main.o 

Vous obtiendrez une erreur, car le compilateur essaiera de résoudre la référence pendante à la fonction “square”, mais ne trouvera aucune définition pour celle-ci. Toutefois, si vous incluez add.o lors de la liaison (la liaison est le processus de résolution de toutes ces références à des fonctions non définies lors de la conversion de fichiers .o en fichiers exécutables ou en fichiers .so), aucun problème ne se produira. c’est à dire

 gcc -o executable main.o add.o 

Voilà donc comment utiliser de manière fonctionnelle des fonctions dans des fichiers C, mais stylistiquement , ce que je viens de vous montrer n’est “pas la bonne façon”. La seule raison pour laquelle je l’ai fait est que je pense que cela vous aidera davantage à comprendre ce qui se passe plutôt que de vous fier à la magie “#include”. Maintenant, vous avez peut-être déjà remarqué que les choses se compliquent si vous devez redéclarer chaque fonction que vous souhaitez utiliser en haut de main.c C’est pourquoi les programmes C utilisent souvent des fichiers auxiliaires appelés “en-têtes” qui ont une extension .h . L’idée d’un en-tête est qu’il ne contient que les déclarations des fonctions, sans leurs définitions. De cette façon, pour comstackr un programme en utilisant les fonctions définies dans add.c, vous n’avez pas besoin de déclarer manuellement chaque fonction que vous utilisez, ni de vous # inclure le fichier add.c complet dans votre code. Au lieu de cela, vous pouvez #include add.h, qui contient simplement les déclarations de toutes les fonctions de add.c.

Maintenant, un rafraîchissement sur #include: #include copie simplement le contenu d’un fichier directement dans un autre. Ainsi, par exemple, le code

 abc #include "wtf.txt" def 

est exactement équivalent à

 abc hello world def 

en supposant que wtf.txt contienne le texte “hello world”.

Donc, si on met toutes les déclarations de add.c dans add.h (ie

 int square(int i); 

puis au sumt de main.c, nous écrivons

 #include "add.h" 

C’est fonctionnellement la même chose que si nous venions de déclarer manuellement la fonction “carré” en haut de main.c.

L’idée générale d’utiliser les en-têtes est donc que vous pouvez avoir un fichier spécial qui déclare automatiquement toutes les fonctions dont vous avez besoin en ne faisant que # l’inclure.

Cependant, les en-têtes ont également une autre utilisation commune. Supposons que main.c utilise des fonctions de 50 fichiers différents. Le sumt de main.c ressemblerait à ceci:

 #include "add.h" #include "divide.h" #include "multiply.h" #include "eat-pie.h" ... 

Au lieu de cela, les gens déplacent souvent tous ces #includes vers le fichier d’en-tête main.h, et simplement #include main.h à partir de main.c. Dans ce cas, le fichier d’en-tête sert à deux choses. Il déclare les fonctions de main.c à utiliser lorsqu’elles sont incluses dans d’autres fichiers, et inclut toutes les dépendances de main.c lorsqu’il est inclus à partir de main.c. Son utilisation permet également des chaînes de dépendances. Si vous incluez add.h, vous obtenez non seulement les fonctions définies dans add.c, mais également implicitement toutes les fonctions utilisées par add.c, toutes les fonctions utilisées, etc.

En outre, plus subtilement, # l’inclusion d’un fichier d’en-tête à partir de son propre fichier .c vérifie implicitement les erreurs que vous avez commises. Si, par exemple, vous avez accidentellement défini carré comme

 double square(int i); 

Dans add.h, vous ne réaliserez normalement pas avant de lier que main.o recherche une définition du carré et add.o en fournit une autre, incompatible . Cela entraînera des erreurs lors de la création de liens, de sorte que vous ne vous en rendrez compte que plus tard dans le processus de construction. Cependant, si vous incluez add.h à partir de add.c, votre fichier se présente comme suit:

 #include "add.h" int square(int i) { return i*i; } 

qui après traitement de l’instruction #include ressemblera à

 double square(int i); int square(int i) { return i*i; } 

Ce que le compilateur remarquera lors de la compilation de add.c, et vous en informera. Effectivement, inclure votre propre en-tête de cette manière vous évite d’annoncer à tort dans d’autres fichiers le type de fonctions que vous fournissez.

Pourquoi vous pouvez utiliser une fonction sans jamais la déclarer

Comme vous l’avez remarqué, dans certains cas, vous pouvez réellement utiliser une fonction sans la déclarer ou la inclure, y compris tout fichier qui le déclare. C’est stupide, et tout le monde s’accorde pour dire que c’est stupide. Cependant, il existe une fonctionnalité héritée du langage de programmation C (et des compilateurs C) qui, si vous utilisez une fonction sans la déclarer au préalable, suppose simplement qu’il s’agit d’une fonction renvoyant le type “int”. Donc, en réalité, utiliser une fonction déclare implicitement cette fonction en tant que fonction qui renvoie “int” si elle n’est pas déjà déclarée. C’est un comportement très étrange si vous y réfléchissez, et le compilateur devrait vous avertir si vous le faites.

Gardes d’en-tête

Une autre pratique courante est l’utilisation de “gardes d’en-tête”. Pour expliquer les gardes d’en-tête, examinons un problème possible. Disons que nous avons deux fichiers: herp.c et derp.c, et qu’ils veulent tous les deux utiliser des fonctions contenues l’une dans l’autre. En suivant les indications ci-dessus, vous pourriez avoir un herp.h avec la ligne

 #include "derp.h" 

et un derp.h avec la ligne

 #include "herp.h" 

Maintenant, si vous y réfléchissez, #include “derp.h” sera converti en contenu de derp.h, qui contient à son tour la ligne #include “herp.h”, qui sera convertie en contenu de herp. h, et cela contient … et ainsi de suite, le compilateur continuera donc à étendre simplement l’include. De même, si main.h # inclut à la fois herp.h et derp.h, et que herp.h et derp.h incluent add.h, nous voyons que dans main.h, nous nous retrouvons avec deux copies de add.h, un à la suite de #including herp.h, et un à la suite de derp.h. Alors, la solution? Un “garde d’en-tête”, c’est-à-dire un morceau de code qui empêche tout # en-tête d’être inclus deux fois. Pour add.h, par exemple, la manière habituelle de procéder est la suivante:

 #ifndef ADD_H #define ADD_H int sqrt(int i); ... #endif 

Ce morceau de code indique essentiellement au préprocesseur (la partie du compilateur qui gère toutes les instructions “#XXX”) de vérifier si “ADD_H” est déjà défini. Si ce n’est pas le cas (si n def), il définit d’abord “ADD_H” (dans ce contexte, ADD_H n’a pas à être défini comme quoi que ce soit, il s’agit simplement d’un booléen défini ou non), puis définit le rest du contenu de l’en-tête. Cependant, si ADD_H est déjà défini, alors # l’inclusion de ce fichier ne fera rien car il n’y a rien en dehors du bloc #ifndef. Donc, l’idée est que ce n’est que la première fois qu’il est inclus dans un fichier qu’il ajoute réellement du texte à ce fichier. Après cela, # l’inclure ne sera pas append de texte supplémentaire à votre fichier. ADD_H est juste un symbole arbitraire que vous choisissez de savoir si add.h a déjà été inclus. Pour chaque en-tête, vous utilisez un symbole différent pour savoir s’il a déjà été inclus ou non. Par exemple, herp.h utiliserait probablement HERP_H au lieu de ADD_H. L’utilisation d’une “protection d’en-tête” résoudra l’un des problèmes énumérés ci-dessus, où vous avez des copies dupliquées d’un fichier inclus, ou une boucle infinie de #includes.

Le problème est que vous ne devriez pas inclure #include un fichier .c.

Pour utiliser une fonction dans un autre fichier, vous devez la déclarer. En général, chaque fichier .c (à l’exception de main.c) est associé à un fichier d’en-tête (.h) qui déclare correctement toutes les fonctions définies dans le fichier .c. Vous pouvez déclarer autant de fois que vous le souhaitez (tant que toutes les déclarations sont identiques), mais il ne peut y avoir qu’une seule définition .

Que se passe-t-il lorsque vous #include "add.c" c’est que le texte de add.c est inclus dans main.c, donnant la définition de main.ca (et en guise de déclaration une déclaration) de add . Ensuite, lorsque vous comstackz add.c seul, cela crée une autre définition de add . Ainsi, il existe deux définitions de la fonction, et le compilateur panique car il ne sait pas laquelle utiliser.

Si vous le changez en #include "add.h" , add.h ressemble à ceci:

 #ifndef ADD_H #define ADD_H extern int add(int x, int y); #endif /* ADD_H - Google "include guard" for more info about this sortingckery */ 

alors main.c a une déclaration de add et peut utiliser la fonction, mais la définition de add n’est fermement définie que dans le fichier add.c; elle n’existe donc qu’une fois. Elle est donc compilée correctement.

Voici un exemple simple d’appel d’une fonction à partir d’un programme c différent

laissez-moi nommer le programme principal en tant que main.c et le programme qui détient la fonction en tant que function.c pour la fonction.c Je crée le fichier d’en-tête appelé function.h

principal c

 #include"function.h" int main() { int a = sum(1,2); return a; } 

fonction.c

 int function(int a,int b) { return a+b; } 

function.h

 int function(int,int); 

Pour comstackr, utilisez la commande ci-dessous

g ++ main.c fonction.c -o main

Voici l’explication détaillée. Dans le programme principal, j’ai appelé la fonction pour additionner 2 nombres. Les valeurs 1 et 2 du programme principal ont été transmises à la fonction de la fonction.c par l’en-tête de la fonction.h qui contient le point d’access ou le pont qui relie la fonction.c

Pour plus de détails, vous pouvez consulter les liens ci-dessous

http://www.cplusplus.com/forum/beginner/34691/

https://social.msdn.microsoft.com/Forums/fr-4ea70f43-a0d5-43f8-8e24-78e90f208110/calling-a-function-fr-a-file-from-another-file?forum=winembplatdev

Ajoutez une instruction print pour vérifier le résultat ou utilisez echo $? après exécution du fichier principal

Vous pouvez l’appeler car une déclaration n’est pas nécessaire pour effectuer un appel en C. Cependant, le type de retour est inconnu et par défaut, int . Cela est possible en partie à cause de la convention d’appel par défaut en C et de la promotion par défaut des types à une précision au moins int .

Si vous incluez un en-tête qui définit la fonction que vous appelez, le compilateur est en mesure de vérifier que les appels à la fonction ont le nombre et le type d’arguments appropriés.

Si vous incluez des définitions de fonction, elles seront exscopes sauf si vous spécifiez leur stockage avec static . Etant donné que vous comstackz et add.c également add.c , vous ne pouvez pas append ceci, car ni l’un ni l’autre de vos fichiers d’object n’exporteront alors add .

Si vous souhaitez simplement inclure toutes vos fonctions, mieux vaut mettre leurs définitions dans des en-têtes et saupoudrer les spécificateurs de stockage sur celles-ci.