gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-04 08:45:37 26 4
gpt4 key购买 nike

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

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

我们有两个文件

main.c
add.c

main.c从add add.c调用了函数 add(int x,int y),但是我在#include add.c之前错误地进行了编译,并且可以正常工作!更令人困惑的是,当我#include add.c时,它在函数add上给出了多定义错误

最佳答案

这里发生了一些不同的事情。首先,我将介绍多个文件的基本编译如何工作。

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

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

另一方面,该声明使您可以向编译器声明您知道某个函数存在,但是您不告诉编译器它是什么。例如,你可以写
int square(int i);

并且编译器希望在其他地方定义函数“square”。

现在,如果您有两个要互操作的文件(例如,假设add.c中定义了函数“square”,并且您要在main.c中调用square(10)),则需要同时进行定义和声明。首先,您在add.c中定义正方形。然后,在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”,但是未在main.o内部定义函数“square”。因此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文件中功能上使用函数的方法,但是从风格上讲,我刚刚向您展示的是“不正确的方法”。我这样做的唯一原因是因为我认为它会更好地帮助您了解正在发生的事情,而不是依靠“#include magic”。现在,您可能之前已经注意到,如果必须在main.c的顶部重新声明要使用的每个函数,情况会有些困惑,这就是为什么C程序经常使用名为“headers”的扩展名为.h的帮助文件。 。 header 的想法是,它仅包含函数的声明,而没有其定义。这样,为了使用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"

这在功能上与我们刚刚在main.c顶部手动声明函数“square”相同。

因此,使用 header 的一般思路是,您可以拥有一个特殊的文件,只需#include即可自动声明所需的所有功能。

但是, header 也有另一种常用的用法。假设main.c使用来自50个不同文件的函数。 main.c的顶部看起来像:
#include "add.h"
#include "divide.h"
#include "multiply.h"
#include "eat-pie.h"
...

取而代之的是,人们经常将所有这些#includes移至main.h头文件,而仅将#include main.h从main.c移至。在这种情况下,头文件用于 两个目的。当其他文件包含它时,它将声明main.c中的函数以供使用;当包含在main.c中时,它将包含main.c的所有依赖项。以这种方式使用它还允许依赖链。如果您#include add.h,不仅可以获取add.c中定义的功能,还可以隐式获取add.c使用的所有功能以及它们使用的所有功能,依此类推。

同样,更巧妙地,#包含自己的.c文件中的头文件会隐式检查您所犯的错误。例如,如果您不小心将正方形定义为
double square(int i);

在add.h中,通常您可能直到链接main.o寻找正方形的定义,而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时会注意到哪些内容,并告诉您有关信息。有效地,以这种方式包含您自己的 header 可以防止您将所提供功能的类型错误地广告给其他文件。

为什么不用声明就可以使用函数

正如您所注意到的,在某些情况下,实际上可以使用函数而不必每次都声明它或#包括任何声明它的文件。这是愚蠢的,每个人都同意这是愚蠢的。但是,它是C编程语言(和C编译器)的一项固有功能,如果不先声明就使用函数,则仅假定它是返回“int”类型的函数。因此,实际上,使用函数隐式将该函数声明为一个函数,如果尚未声明该函数,则该函数将返回“int”。如果您考虑一下这是非常奇怪的行为,并且编译器在执行该行为时会警告您。

header 卫队

另一种常见的做法是使用“Header Guards”。为了解释标题保护,让我们看一个可能的问题。假设我们有两个文件:herp.c和derp.c,它们都想使用彼此包含的函数。按照上述准则,您可能会在以下一行中找到herp.h:
#include "derp.h"

和derp.h与行
#include "herp.h"

现在,如果您考虑一下,将#include“derp.h”转换为derp.h的内容,而该文件又包含#include“herp.h”行,该行将转换为herp的内容。 h,并且包含...等等,因此编译器将仅在扩展includes的情况下永久运行。同样,如果main.h#同时包含herp.h和derp.h,而herp.h和derp.h都包含add.h,则在main.h中,我们将看到两个add.h副本,一个是由于#include herp.h的结果,另一个是由于包含derp.h的结果。那么,解决方案呢? “标题保护程序”,即一段代码,可防止#header被#include两次。例如,对于add.h,正常的操作方法是:
#ifndef ADD_H
#define ADD_H

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

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

关于c - 从C中同一目录中的另一个文件调用函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6618921/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com