从C中同一目录中的另一个文件调用函数

我正在学习C,但我对Java等高级编程语言有很长的经验。

我正在阅读有关头文件的内容,所以我正在玩它们,但是我注意到我可以从另一个文件调用一个函数而不用#including它(它在同一个目录中),这怎么可能?! 是make文件,配置那种方式的链接器还是什么?

我们有两个文件

main.c add.c 

main.c从add add.c调用函数add(int x,int y) ,但是在#including add.c之前我错误地编译了它并且它工作了! 更令人困惑的是,当我#include add.c时,它会在函数add上产生多重定义错误

这里有几件不同的事情。 首先,我将介绍多个文件的基本编译是如何工作的。

如果你有多个文件,重要的是声明和函数定义之间的区别。 定义可能是您在定义函数时习惯的定义:您编写函数的内容,如

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

另一方面,声明允许您向编译器声明您知道函数存在,但是您不告诉编译器它是什么。 例如,你可以写

 int square(int i); 

并且编译器会期望函数“square”在别处定义。

现在,如果你有两个不同的文件想要互操作(例如,假设函数“square”在add.c中定义,并且你想在main.c中调用square(10)),你需要同时做一个定义和一个声明。 首先,在add.c中定义square。 然后,在main.c的开头声明它。 这让编译器知道它在编译main.c时有一个函数“square”,它在别处定义。 现在,您需要将main.c和add.c编译为目标文件。 你可以通过电话来做到这一点

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

这将生成文件main.o和add.o. 它们包含已编译的函数,但不完全可执行。 这里要理解的重要一点是,main.o在某种意义上是“不完整的”。 在编译main.o时,你告诉它函数“square”存在,但函数“square”没有在main.o中定义。 因此main.o对函数“square”有一种“悬空引用”。 除非将它与另一个包含“square”定义的.o(或.so或.a)文件合并,否则它不会编译成完整的程序。 如果你只是尝试 main.o 链接到一个程序,即

 gcc -o executable main.o 

您将收到错误,因为编译器将尝试解析对“square”函数的悬空引用,但不会找到它的任何定义。 但是,如果在链接时包含add.o(链接是将.o文件转换为可执行文件或.so文件时解析所有这些对未定义函数的引用的过程),那么就不会有任何问题。 即

 gcc -o executable main.o add.o 

这就是如何在C文件中function性地使用函数,但在风格上 ,我刚刚向您展示的是“不正确的方式”。 我做的唯一原因是因为我认为它会更好地帮助你了解正在发生的事情,而不是依赖于“#include magic”。 现在,您可能已经注意到,如果您必须重新声明要在main.c顶部使用的每个函数,事情会变得有点混乱。这就是为什么C程序经常使用名为“headers”的辅助文件,其扩展名为.h 。 标题的概念是它只包含函数的声明, 而没有它们的定义。 这样,为了使用add.c中定义的函数编译程序,您不需要手动声明您正在使用的每个函数,也不需要#include代码中的整个add.c文件。 相反,你可以#include add.h,它只包含add.c的所有函数的声明。

现在,重新开始#include:#include只是将一个文件的内容直接复制到另一个文件中。 所以,例如,代码

 abc #include "wtf.txt" def 

完全等同于

 abc hello world def 

假设wtf.txt包含文本“hello world”。

所以,如果我们把add.c的所有声明都放在add.h中(即

 int square(int i); 

然后在main.c的顶部,我们写

 #include "add.h" 

这在function上与我们刚刚在main.c顶部手动声明函数“square”一样。

所以使用标题的一般想法是你可以有一个特殊的文件,通过#including它自动声明你需要的所有函数。

但是,标题还有一个更常见的用途。 我们假设main.c使用50个不同文件的函数。 main.c的顶部看起来像:

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

相反,人们经常将所有#includes移动到main.h头文件中,只需从main.c中#include main.h. 在这种情况下,头文件有两个目的。 它声明了main.c中的函数,供其他文件包含时使用, 并且包含main.c中包含的main.c的所有依赖项。 以这种方式使用它也允许依赖 。 如果#include add.h,不仅可以获得add.c中定义的函数,还可以隐式获取add.c使用的任何函数,以及它们使用的任何函数,等等。

另外,更巧妙的是,#包含来自它自己的.c文件的头文件会隐式检查你所犯的错误。 例如,如果您不小心将square定义为

 double square(int i); 

在add.h中,你通常可能没有意识到,直到你链接main.o正在寻找square的一个定义,add.o正在提供另一个不兼容的 。 这会导致链接时出错,因此在构建过程的后期才会意识到错误。 但是,如果你从add.c #include add.h到编译器,你的文件看起来像

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

处理完#include语句之后会是这样的

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

编译add.c时编译器会注意到哪些,并告诉你。 实际上,以这种方式包括您自己的标题可以防止您错误地向其他文件宣传您提供的function类型。

为什么你可以在没有声明的情况下使用函数

正如您所注意到的,在某些情况下,您实际上可以使用函数而不是每次声明它或#including任何声明它的文件。 这是愚蠢的,每个人都同意这是愚蠢的。 但是,它是C编程语言(和C编译器)的遗留特性,如果你在没有首先声明它的情况下使用函数,它只是假设它是一个返回类型为“int”的函数。 所以实际上,使用函数隐式地将该函数声明为一个函数,如果它尚未声明,则返回“int”。 如果你考虑它,这是非常奇怪的行为,编译器应警告你,如果你做这种行为。

头卫

另一种常见做法是使用“Header Guards”。 为了解释标题保护,让我们看看可能存在的问题。 假设我们有两个文件:herp.c和derp.c,它们希望使用彼此包含的函数。 遵循上述准则,您可能会有一行herp.h

 #include "derp.h" 

和一行derp.h

 #include "herp.h" 

现在,如果你考虑一下,#include“derp.h”将被转换为derp.h的内容,而derp.h又包含#include“herp.h”行,它将被转换为herp的内容。 h,包含……等等,所以编译器将继续只是扩展包含。 类似地,如果main.h#包括herp.h和derp.h,并且herp.h和derp.h都包含add.h,我们看到在main.h中,我们最终得到了两个 add.h副本,一个是#including herp.h的结果,另一个是包含derp.h的结果。 那么,解决方案呢? 一个“Header guard”,即一段防止任何标头被#included两次的代码。 例如,对于add.h,执行此操作的常规方法是:

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

这段代码基本上是告诉预处理器(处理所有“#XXX”语句的编译器部分)来检查是否已经定义了“ADD_H”。 如果不是(如果是n def)那么它首先定义“ADD_H”(在此上下文中,ADD_H不必定义任何东西,它只是一个定义或不定义的布尔值),然后定义标题的其余内容。 但是,如果已经定义了ADD_H,则#including此文件将不执行任何操作 ,因为#ifndef块之外没有任何内容。 因此,我们的想法是,只有第一次将它包含在任何给定文件中时,它才会实际向该文件添加任何文本。 之后,#include它不会在文件中添加任何其他文本。 ADD_H只是您选择的任意符号,用于跟踪add.h是否已包含在内。 对于每个标题,您使用不同的符号来跟踪它是否已被包含。 例如,herp.h可能会使用HERP_H而不是ADD_H。 使用“标题保护”将解决我上面列出的任何问题,其中包含文件的重复副本,或#includes的无限循环。

问题是你不应该#include一个.c文件。

要在另一个文件中使用函数,您需要声明它。 通常,每个.c文件(main.c除外)都有一个关联的头文件(.h),它正确地声明了.c文件中定义的所有函数。 您可以根据需要声明多次(只要所有声明都相同),但只能有一个定义

#include "add.c"时会发生的事情是#include "add.c"的文本包含在main.c中,给出了main.ca 定义 (以及作为副作用的声明) add 。 然后,当你自己编译add.c时,会创建另一个 add定义。 因此,函数有两个定义,编译器因为不知道使用哪一个而吓坏了。

如果将其更改为#include "add.h" ,其中add.h看起来像这样:

 #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 */ 

然后main.c有一个add的声明并且可以使用该函数,但是add定义只在add.c文件中非常牢固,所以它只存在一次,所以它会正确编译。

这是一个从不同的c程序调用函数的简单示例

让我将主程序命名为main.c,将函数命名为function.c的函数。我正在创建名为function.h的头文件

main.c中

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

function.c

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

function.h

 int function(int,int); 

要编译,请使用下面给出的命令

g ++ main.c function.c -o main

这里详细解释。 在主程序中,我调用了函数来求和2个数。 主程序中的值1和2通过头函数h提供给function.c中的函数,头函数h保存访问点或函数c的桥。

有关详细信息,请查看下面给出的链接

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

添加print语句来检查结果或使用echo $? 执行文件main后

您可以调用它,因为在C中调用不需要声明。但是返回类型是未知的,因此默认为int 。 这可能部分是由于C中的默认调用约定,以及类型的默认提升至少为int precision。

如果包含定义要调用的函数的标头,则编译器能够检查对函数的调用是否具有正确的参数数量和类型。

如果包含函数定义,则除非您使用static指定其存储,否则将导出它们。 由于您还在编译和链接add.c ,因此您无法添加此项,因此您的目标文件中的任何一个或两个都不会导出add

如果您想简单地包含所有函数,最好将它们的定义放入标题中,并在它们上面添加存储说明符。