Moh*_*Ezz 23 c linker gcc header
我正在学习C,但我对Java等高级编程语言有很长的经验.
我正在阅读有关头文件的内容,所以我正在玩它们,但是我注意到我可以从另一个文件调用一个函数而不用#including它(它在同一个目录中),这怎么可能?是make文件,配置那种方式的链接器还是什么?
我们有两个文件
main.c
add.c
Run Code Online (Sandbox Code Playgroud)
main.c add(int x,int y)
从add add.c 调用函数,但是在#including add.c之前我错误地编译了它并且它工作了!更令人困惑的是,当我#include add.c时,它会在函数add上产生多重定义错误
Jer*_*wen 86
这里有几件不同的事情.首先,我将介绍多个文件的基本编译是如何工作的.
如果你有多个文件,重要的是声明和函数定义之间的区别.定义可能是您在定义函数时习惯的定义:您编写函数的内容,如
int square(int i) {
return i*i;
}
Run Code Online (Sandbox Code Playgroud)
另一方面,声明允许您向编译器声明您知道函数存在,但是您没有告诉编译器它是什么.例如,你可以写
int square(int i);
Run Code Online (Sandbox Code Playgroud)
并且编译器会期望函数"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
Run Code Online (Sandbox Code Playgroud)
这将生成文件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
Run Code Online (Sandbox Code Playgroud)
您将收到错误,因为编译器将尝试解析对"square"函数的悬空引用,但不会找到它的任何定义.但是,如果在链接时包含add.o(链接是将.o文件转换为可执行文件或.so文件时解析所有这些对未定义函数的引用的过程),那么就不会有任何问题.即
gcc -o executable main.o add.o
Run Code Online (Sandbox Code Playgroud)
这就是如何在C文件中功能性地使用函数,但在风格上,我刚刚向您展示的是"不正确的方式".我做的唯一原因是因为我认为它会更好地帮助你理解正在发生的事情,而不是依赖于"#include magic".现在,您可能已经注意到,如果您必须重新声明要在main.c顶部使用的每个函数,事情会变得有点混乱.这就是为什么C程序经常使用名为"headers"的辅助文件,其扩展名为.h .一个头的想法是,它包含只是功能的声明,没有他们的定义.这样,为了使用add.c中定义的函数编译程序,您不需要手动声明您正在使用的每个函数,也不需要#include代码中的整个add.c文件.相反,你可以#包括add.h,其中只包含了声明的add.c.的所有功能
现在,重新开始#include:#include只是将一个文件的内容直接复制到另一个文件中.所以,例如,代码
abc
#include "wtf.txt"
def
Run Code Online (Sandbox Code Playgroud)
完全等同于
abc
hello world
def
Run Code Online (Sandbox Code Playgroud)
假设wtf.txt包含文本"hello world".
所以,如果我们把add.c的所有声明都放在add.h中(即
int square(int i);
Run Code Online (Sandbox Code Playgroud)
然后在main.c的顶部,我们写
#include "add.h"
Run Code Online (Sandbox Code Playgroud)
这在功能上与我们刚刚在main.c顶部手动声明函数"square"相同.
所以使用标题的一般想法是你可以有一个特殊的文件,通过#including它自动声明你需要的所有函数.
但是,标题还有一个更常见的用途.我们假设main.c使用50个不同文件的函数.main.c的顶部看起来像:
#include "add.h"
#include "divide.h"
#include "multiply.h"
#include "eat-pie.h"
...
Run Code Online (Sandbox Code Playgroud)
相反,人们经常将所有#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);
Run Code Online (Sandbox Code Playgroud)
在add.h中,你通常可能没有意识到,直到你链接main.o正在寻找square的一个定义,add.o提供另一个,不兼容的.这会导致链接时出错,因此在构建过程的后期才会意识到错误.但是,如果你从add.c #include add.h到编译器,你的文件看起来像
#include "add.h"
int square(int i) {
return i*i;
}
Run Code Online (Sandbox Code Playgroud)
在处理完#include语句之后会是这样的
double square(int i);
int square(int i) {
return i*i;
}
Run Code Online (Sandbox Code Playgroud)
编译add.c时编译器会注意到哪些,并告诉你.实际上,以这种方式包含您自己的标题可以防止您错误地向其他文件宣传您提供的功能类型.
为什么你可以在没有声明的情况下使用函数
正如您所注意到的,在某些情况下,您实际上可以使用函数而不是每次声明它或#including任何声明它的文件.这是愚蠢的,每个人都同意这是愚蠢的.但是,它是C编程语言(和C编译器)的一个遗留特性,如果你使用一个函数而不首先声明它,它只是假设它是一个返回类型为"int"的函数.所以实际上,使用函数隐式地将该函数声明为一个函数,如果它尚未声明,则返回"int".如果你考虑它,这是非常奇怪的行为,编译器应警告你,如果你做它的行为.
头卫
另一种常见做法是使用"Header Guards".为了解释标题保护,让我们看看可能存在的问题.假设我们有两个文件:herp.c和derp.c,它们都希望使用彼此包含的函数.遵循上述准则,您可能会有一行herp.h
#include "derp.h"
Run Code Online (Sandbox Code Playgroud)
和一行derp.h
#include "herp.h"
Run Code Online (Sandbox Code Playgroud)
现在,如果你考虑一下,#include"derp.h"将被转换为derp.h的内容,而derp.h又包含#include"herp.h"行,它将被转换为herp的内容.小时,即包含...等等,所以编译器将永远持续下去只是扩大包括.类似地,如果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
Run Code Online (Sandbox Code Playgroud)
这段代码基本上是告诉预处理器(处理所有"#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的无限循环.
归档时间: |
|
查看次数: |
56354 次 |
最近记录: |