c语言 - 如何在函数文件和项目文件中正确包含自己的库



我在尝试Exercise 8-3K&R时遇到了困难,练习的目标是重写stdio.h的一些函数,如fopenfclosefillbufflushbuf以下是我的源文件的组织方式:

stdio.h:包含类型和宏定义,以及一些适用于库的函数的声明。 文件的所有内容都包含在#ifndef#endif行之间,如下所示:

#ifndef STDIO_H
#define STDIO_H
/* content of stdio.h */
#endif

myfunction.c:我每个函数都有一个.c文件,每个文件都有一个#include "stdio.h"行来加载所有需要的类型定义。

main.c:在我有代码来测试我的函数的地方,main.c也有一行#include "stdio.h"

我的问题是:当我尝试使用gcc编译所有文件时,我运行到错误:

multiple definition of `_iob'

在包含我的stdio.h的每个函数文件中,(_iob是我仅在stdio.h中定义的变量)...为什么会这样?我认为#ifndef行是专门防止此类错误。

更一般地说:

  1. 您将如何制作自己的头文件和库/函数文件并在项目中使用它们?
  2. 有没有办法让链接器通过包含头文件来找出我的函数的位置,就像标准函数一样?

请注意库与其头文件之间的区别。

库是二进制机器码的(集合)(带有一些额外的元数据,例如链接器的重定位指令)。

例如,在我的 Linux 系统上,动态库通常是共享对象(例如/usr/lib/x86_64-linux-gnu/libgmp.so),尝试一些像#include "libgmp.so" //wrong这样的预处理器指令是完全没有意义的。

但是库有一些 API。该 API 由一些文档和一些头文件提供,例如gmp.h,你应该#include "gmp.h"使用它的任何 C 代码(你的 C 翻译单元)。

myfunction.c:我每个函数都有一个 .c 文件

每个函数有一个文件通常很糟糕。通常可以对相关函数进行分组。例如,在您的情况下,您可能希望在同一个myopenclose.c翻译单元中定义myfopenmyfclose函数(即使您不必这样做),因为这两个函数密切相关。根据经验,我更喜欢每个源文件有一行或几千行(但这确实是一个品味问题,有些人喜欢有很多小文件)。

请记住,编译器真正看到的是预处理形式的代码。考虑要求你的编译器生成该形式(例如,从foo.c你可以通过gcc -C -E -Wall foo.c > foo.i在我的 Linux 桌面上获得其预处理的表单foo.i)并研究它。在您自己的文件上尝试这样做(例如,如果您有myopenclose.c的话)。

如果您有许多小文件,编译器可能会在每个文件中包含相同的标头,并且每次都会编译这些包含的声明。顺便说一句,请注意gcc只是一个驱动程序。将其与-v标志一起使用。您将看到它正在运行cc1(C 编译器本身)、as(汇编器)、ld(链接器)等。

我运行到错误:

"_iob"的多重定义

在包含我的 stdio.h 的每个函数文件中,(_iob是我只在 stdio.h 中定义的变量)。

您可能应该在stdio.h声明extern_iob全局变量,并仅在库的一个实现文件(如果相关,可能是myopenclose.c)中定义全局_iob

不要混淆定义声明(变量、函数、类型等)。花一些时间阅读 C11 标准 n1570。这些词在那里定义。根据经验,声明应该进入头.h文件、实现中的定义(变量和函数).c文件(当然细节要复杂得多,你经常但不总是在头文件中定义类型和struct)。

我强烈建议使用一些Linux发行版(它对开发人员和学生非常友好)并研究一些现有的自由软件C标准库的源代码(如musl-libc,其代码非常可读)。更一般地说,研究现有自由软件项目的源代码(例如在github上)。他们会激励你。

有没有办法让链接器通过包含头文件来找出我的函数的位置,就像标准函数一样?

这显示了很多混乱(上面的问题没有任何意义)。阅读更多关于编译器(你的cc1程序 - 由gcc启动 - 正在将.c文件转换为某个目标文件.o)和链接器(你的ld,通常由gcc启动,正在聚合几个目标文件,处理其中的重定位,并生成ELF库或可执行文件)。预处理(例如#include指令)在编译时由cc1完成。链接器看不到任何头文件(它只处理对象文件或库)。

如果重写一些系统声明和函数,同时包括系统声明,则可能会发生一些冲突。

头文件(.h)包含代码(通常只有声明),你描述的机制(#ifndef STDIO_H)是防止同一个头文件的多个包含 - 主要是因为另一个已经加载的包含文件(头)也可能包含它。这会导致与您相同的碰撞。

例如,在 C 中,您可以

  • 创建一个包含您自己的声明的新头文件 + 不会与您的声明冲突的 STDIO 文件

  • 使用 stdio
  • 声明,并且只编写使用与 stdio 相同的结构、定义、枚举等的新函数

  • 重写必要的声明和代码,允许您不再包含系统标头

  • 使用其他命名约定,例如在头文件和代码中使用my_iob

最后两个可能是您最好的,因为您仍然有一些来自头文件的冲突。

例如,您的代码可能不包含stdio.h,但您包含的另一个头文件可能会间接地执行此操作...

最新更新