4

At first, I was writing my function in an .h file and then include it with #include "myheader.h". Then, someone said me that it's better to add to these files only functions prototypes and to put the real code in a separate .c file. Now, I'm able to compile more .c files to generate only an executable, but at this point I can't understand why I should add the header files if the code is in another file.

Moreover, I had a look to standard C libraries (like stdlib.h) in the system and it seemed to me to store only structure definitions, constants and similar... I'm not so good with C (and to be honest, stdlib.h was almost Chinese to me, no offense for Chinese of course :) ), but I didn't spot any single line of 'operative' code. However, I always just include it without adding anything else and I compile my files as if the 'code' was actually there.

Can someone please explain me how do these things work? Or, at least, point me to a good guide? I searched on Google and SO, also, but didn't find anything that explains it clearly.

4

5 回答 5

16

当您编译 C 代码时,编译器必须实际知道存在具有定义名称、参数列表、返回类型和可选修饰符的特定函数。所有这些东西都称为函数签名,并且在头文件中声明了特定函数的存在。有了这些信息,当编译器找到对该函数的调用时,它就会知道要查找哪种类型的参数,可以控制它们是否具有适当的类型,并准备好一个结构,该结构将在代码实际执行之前被压入堆栈跳转到你的函数实现。但是编译器不必知道函数的实际实现,它只是在目标文件中为所有函数调用放置一个“占位符”。(注意:每个 c 文件都编译为一个目标文件)。#includesimple 获取头文件并将该#include行替换为文件的内容。

编译后,构建脚本将所有目标文件传递给链接器。链接器将解析所有函数“占位符”,找到函数实现的物理位置,让它们位于您的目标文件、框架库或 dll 中。它简单地将信息放在所有函数调用中可以找到函数实现的位置,因此您的程序将知道当它到达您的函数调用时在哪里继续执行。

说了这么多,应该清楚为什么不能将函数定义放在头文件中。如果稍后您#include将此标头放入多个 c 文件中,它们都会将函数实现编译为两个单独的目标文件。编译器可以很好地工作,但是当链接器想要将所有内容链接在一起时,它会找到该函数的两个实现并给你一个错误。

stdlib.h和朋友的工作方式相同。可以在框架库中找到其中声明的函数的实现,即使您不知道,编译器也会“自动”链接到您的代码。

于 2012-06-22T13:50:26.730 回答
3

C 语言(与 C++ 一起)使用一种相当过时的策略来使编译器知道在别处定义的函数。

这个策略是这样的:函数的签名等(这些东西在 C 中称为声明)进入一个名为 header 的特殊文件,并且每个想要使用它们的其他文件几乎都应该将该 header 包含在文件中(实际上,#include指令只是告诉编译器包含标题的文字),以便编译器再次看到函数声明。

其他语言以不同的方式解决这个问题:编译器查看所有源代码,并记住已编译类本身的元数据。

C 中使用的策略将查找所有依赖项的任务从编译器转移到了开发人员;这是旧时代计算机又小又笨又慢的遗留问题,因此开发人员的这种帮助非常有价值。

尽管这种策略有很多缺点,而且除了理论上现在可以更改之外,标准不会更改,因为已经以这种风格编写了千兆字节的代码。

tl; dr:这是 70 年代的遗产。

于 2012-06-22T13:32:53.920 回答
1

在 C 中,需要在调用函数之前声明函数。需要这样做的原因是,在 70 年代,首先解析一个文件的所有符号然后再解析它以实际编译代码会花费太多时间。如果在调用所有函数之前声明所有函数,一次解析就足够了。然而,在现代系统中,我们不再面临这些限制,这就是现代语言没有这个要求的原因。

Imagine you have 2 files a.c b.c in your project. You implement a function foo which you want to use in both files. You can't just define the function in a.c and use it in b.c since you have to declare a function before you call it. So you would add a line void foo(); to b.c. But everytime you change the signature of your function in a.c you would have to change the declaration in b.c. To circumvent this issue it is standard strategy in C to declare all functions your file implements in a seperate header file (in this case a.h. The header file is then included by all other files who want to use that code (so b.c would use this: #include "a.h").

于 2012-06-22T13:43:42.723 回答
0

An#include是一个预处理器指令,它使文件以文本方式插入到#include发生的位置。

链接多个包含相同头文件的 .c 文件时,必须注意避免包含多个头文件(以文本形式多次插入头文件)。、#ifndef和预处理器指令#define#endif用于防止多重包含。

#ifndef MY_FILE_H
#define MY_FILE_H

/* This code will not be included more than once. */

#endif /* !MY_FILE_H */
于 2012-06-22T14:03:03.200 回答
0

如果代码在另一个文件中,我不明白为什么要添加头文件。

头文件包含在另一个文件中定义的函数的声明,这是调用函数的代码正确编译所必需的。

例如,假设我编写以下代码:

int main(void)
{
  double *foo = malloc(sizeof *foo * 10);
  if (foo)
  {
    // do something with foo
    free (foo);
  }
  return 0;
}

malloc是一个标准库函数,它动态分配内存并返回一个指向它的指针。的返回类型mallocvoid *,它的任何值都可以分配给任何其他指针类型。 free是另一个释放通过 分配的内存的标准库函数,malloc它的返回类型是void(IOW,无返回值)。

但是,编译器不会自动知道什么mallocfree返回(或不返回);它需要在当前范围内查看两个函数的声明,然后才能正确转换函数调用。在 C89 和更早的标准下,如果调用函数时没有在范围内声明,编译器会假定函数返回int; 因为int不兼容double *(你不能在没有演员表的情况下直接将一个分配给另一个),你会得到一个“不兼容的分配”诊断。在 C99 及更高版本下,根本不允许隐式声明。无论哪种方式,编译器都不会翻译代码。

我需要添加行

#include <stdlib.h>

其中包括文件开头的和(以及一堆其他东西)的声明 mallocfree

您不想将函数定义(或变量定义)放在头文件中的原因有很多。假设您foo在标题中定义函数 ah 您包含a.h在文件a.cb.c. 每个文件都可以单独编译,但是当您尝试将它们链接在一起以构建库或可执行文件时,您将从链接器收到“多重定义”错误——您最终创建了两个单独的函数实例同名,这是一个禁忌。变量定义 也是如此。

它也不能很好地扩展。如果您将一堆函数放在它们自己的头文件中并将它们包含在一个源文件中,那么您将所有这些函数都翻译在一个大文件中。此外,如果您只更改源文件或一个头文件中的代码,则每次重新编译 .c 文件时仍会重新编译所有内容。通过将每个函数放在它自己的 .c 文件中,您可以通过仅重新编译需要重新编译的文件来减少总体构建时间。

于 2012-06-22T15:36:06.457 回答