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

How to correctly include own libraries in function files and project files

我在尝试做 K&R 的 Exercise 8-3 时卡住了,练习的目标是重写 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. 您将如何制作自己的头文件和 library/function 文件并在您的项目中使用它们?
  2. 有没有办法让链接器通过包含头文件来确定我的函数的位置,就像它对标准函数所做的一样?

请注意 library and its header files.

之间的区别

库是指向链接器的(collection 个)二进制 machine code (with some additional meta-data, e.g. relocation 指令。

例如,在我的 Linux 系统上,动态库通常共享 objects(例如 /usr/lib/x86_64-linux-gnu/libgmp.so)并且尝试一些像 #include "libgmp.so" //wrong.

但是图书馆有一些 API。 API 由一些文档和一些 header 文件给出,例如gmp.h 并且你应该在任何使用它的 C 代码(你的 C 翻译单元)中 #include "gmp.h"

myfunction.c: I have a .c file per function

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

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

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

I run to the error:

multiple definition of `_iob'

on every one of my function files where my stdio.h is included, (_iob is a variable I only defined inside my stdio.h).

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

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

我强烈建议使用一些 Linux 发行版(它对开发人员和学生非常友好)并且 研究 一些 现有的源代码 免费软件 C standard library (like musl-libc, whose code is quite readable). More generally, study the source code of existing free software projects (e.g. on github)。他们会激励你。

Is there a way to make the linker figure out the position of my functions just by including the header file, the same way it does for standard functions ?

这说明很多地方很混乱(上面的问题没有任何意义)。阅读更多关于 compilers (your cc1 program -started by gcc- is translating a .c file into some object file .o) and about linkers (your ld, generally started by gcc, is agglomerating several object files, processing relocations inside them, and producing an ELF library or an executable). The preprocessing 的内容(例如 #include 指令)是由 cc1 在编译时完成的。链接器看不到任何 header 文件(它只处理 object 文件或库)。

如果您重写一些系统声明和函数,同时包含系统声明,您可能会遇到一些冲突。

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

例如,在 C 中,您可以

  • 制作一个新的头文件,其中包含您自己的声明 + 不与您的冲突的 stdio 声明

  • 使用 stdio 声明,并且只编写使用与 stdio

  • 相同的结构、定义、枚举等的新函数
  • 重写必要的声明和代码,让您不再包含系统头文件

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

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

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