web-dev-qa-db-fra.com

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, alors 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) depuis 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

21
MohamedEzz

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, disons que la fonction "square" est définie dans add.c, et que vous voulez appeler square (10) dans main.c), vous devez do both une définition et une déclaration. Tout d'abord, vous définissez square dans add.c. Ensuite, vous déclarez-le au début de main.c. Ceci permet au compilateur de savoir, lorsqu’il compile main.c, qu’il existe une fonction "carré" définie ailleurs. Maintenant, vous devez compiler main.c et add.c dans des fichiers objets. 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 compilera pas dans un programme complet à moins que vous ne le combiniez 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.-à-d.

gcc -o executable main.o

Vous obtiendrez une erreur, car le compilateur essaiera de résoudre la référence suspendue à la fonction "carré", 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 les références à des fonctions non définies lors de la conversion de fichiers .o 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

C'est donc ainsi que fonctionnellement utilise des fonctions sur 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, c’est que je pense que cela vous aidera mieux à comprendre ce qui se passe plutôt que de vous fier à «la magie à l’inclusion». Maintenant, vous avez peut-être déjà remarqué que les choses se gâtent si vous devez redéclarer chaque fonction que vous voulez 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 contient seulement les déclarations des fonctions, sans leurs définitions. Ainsi, pour compiler 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 d’inclure tout le fichier add.c dans votre code. Au lieu de cela, vous pouvez #include add.h, qui contient simplement les declarations de toutes les fonctions de add.c. 

Maintenant, un rappel 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 nous mettons toutes les déclarations de add.c dans add.h (i.e.

int square(int i);

puis au sommet 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.

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

Cependant, les en-têtes ont également une autre utilisation courante. Supposons que main.c utilise des fonctions de 50 fichiers différents. Le sommet 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 -/fins. Il déclare que les fonctions de main.c sont utilisées lorsqu'elles sont incluses dans d'autres fichiers. Et elles incluent toutes les dépendances de main.c lorsqu'elles sont incluses à partir de main.c. Son utilisation de cette façon permet également de chaines 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 par ils, 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 faites. Si, par exemple, vous avez accidentellement défini carré comme.

double square(int i);  

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

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

#include "derp.h"

#include "herp.h"

#ifndef ADD_H
#define ADD_H

int sqrt(int i);
...
#endif

This piece of code is essentially telling the preprocessor (the part of the compiler which handles all of the "#XXX" statements) to check if "ADD_H" is already defined. If it isn't (if n def) then it first defines "ADD_H" (in this context, ADD_H doesn't have to be defined as anything, it is just a boolean which is either defined or not), and then defines the rest of the contents of the header. However, if ADD_H is already defined, then #including this file will do nothing, because there is nothing outside of the #ifndef block. So the idea is that only the first time it is included in any given file will it actually add any text to that file. After that, #including it will not add any additional text to your file. ADD_H is just an arbitrary symbol you choose to keep track of whether add.h has been included yet. For every header, you use a different symbol to keep track of whether it has been included yet or not. For example, herp.h would probably use HERP_H instead of ADD_H. Using a "header guard" will fix any of the problems I listed above, where you have duplicate copies of a file included, or an infinite loop of #includes.

81
Jeremy Salwen

Le problème est que vous ne devriez pas être #includeing un fichier .c.

Pour utiliser une fonction dans un autre fichier, vous devez la déclarer. En règle générale, 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'un seul définition .

Que se passe-t-il lorsque vous #include "add.c" est que le texte de add.c est inclus dans main.c, donnant à main.c un definition (et, en tant qu’effet secondaire, une déclaration) de add. Ensuite, lorsque vous compilez add.c seul, cela crée un 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 trickery */

then main.c a une déclaration de add et peut utiliser la fonction, mais la definition de add est assez fermement uniquement dans le fichier add.c, elle n’existe donc qu’une seule fois et se compile correctement.

4
Chris Lutz

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 compiler, 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’accès ou le pont qui mène à 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/en-US/4ea70f43-a0d5-43f8-8e24-78e90f208110/calling-a-function-in-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

2
Shameerariff

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 sera donc par défaut à int. Cela est possible en partie en raison de la convention d'appel par défaut en C et de la promotion par défaut des types à une précision d'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 exportées sauf si vous spécifiez leur stockage avec static. Etant donné que vous compilez et liez également add.c, vous ne pouvez pas ajouter ceci, car ni l'un ni l'autre de vos fichiers d'objet 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 des spécificateurs de stockage sur celles-ci.

1
Matt Joiner