对于由C开发的大型软件,我们首先在单独的头文件中声明所有自定义函数(例如myfun.h
)。之后,一旦我们编写了使用main.c
中列出的函数的代码(例如myfun.h
),我们就必须#include "myfun.h"
。我想知道它是如何工作的,因为即使我在主体之前包含在头文件中声明的函数名,代码也无法在main.c
中看到函数详细信息。我想它会搜索图书馆以获取功能细节......我是对的吗?
答案 0 :(得分:1)
当你说"它将在图书馆中搜索功能细节"你离我不远,但这不是很正确。函数声明,即函数原型只包含足够的信息,编译器可以做两件事:
首先,编译器会将该函数注册为已知标识符,以便它在您调用它时知道您正在采取什么,而不是带括号的随机字母串(对于编译器,如果没有函数原型,它们本质上是相同的 - 一个错误)。
其次,编译器使用函数原型来检查代码的正确性。在这个意义上的正确性意味着函数调用将匹配arity和type中的原型。换句话说,对int square(int a, int b);
的函数调用将有两个参数,都是整数。
该计划没有搜索图书馆,"虽然。没有括号的函数名称不是函数调用,而是函数的地址。因此,当您调用函数时,处理器会跳转到函数的内存位置。 (这假设函数没有内联。)
这个功能位于哪里?这取决于。如果你在同一个模块中编写了这个函数,即将一个.c文件编译成一个与main.c文件链接的对象到一个可执行文件中,那么该函数的位置将在.TEXT部分的某处。可执行文件。换句话说,它只是略微偏离主要功能的入口点。在一个巨大的项目中,这个偏移量不会那么轻微,但它会比单独物体的偏移量短。
话虽如此,如果您将此假设函数编译成您从主程序调用的DLL,那么函数的地址将以两种方式之一确定:
回到预处理器,注意两件事情很重要。首先,它在任何编译发生之前运行。这个很重要。由于该程序并未真正被编译和#34;当预处理器正在做它的事情时,宏不是类型安全的。 (插入关于C&#34的Haskell笑话;键入安全" )这就是为什么你不应该 - 或者不应该在C ++中看到宏。 C中的宏可以通过const
和C ++中的内联函数完成,并且具有类型安全性的额外好处。
其次,预处理器几乎只是一个搜索和替换引擎。例如,在以下代码中,没有任何反应,因为预处理器if
语句的计算结果为false,因为我从未定义过任何内容。预处理器删除了本节中的代码。请记住,由于编译器尚未认真运行,因此不会编译此删除的代码。这个事实通常用于实现调试或登录调试版本的函数。在发布版本中,然后操纵预处理器定义,以便不包括调试代码。
#include <stdio.h>
#include <stdlib.h>
int main()
{
#if TRUE
printf("Hello, World!");
#endif
return EXIT_SUCCESS;
}
实际上,我使用的EXIT_SUCCESS
宏在stdlib.h中定义,并由0
替换。 (EXIT_FAILURE = 1)。
在当天,预处理器被用作胶带,基本上是为了补偿C中的故障。
例如,由于const值不能用作数组大小,因此使用了宏,如下所示:
// Not valid C89, possibly even C99
const int DEFAULT_BUFFER_SIZE = 128;
char user_input[DEFAULT_BUFFER_SIZE];
// Legal since the dawn of time
#define DEFAULT_BUFFER_SIZE 128
char user_input[DEFAULT_BUFFER_SIZE];
预处理器的另一个重要用途是代码可移植性,例如:
#ifdef WIN32
// Do windows things
#elif
// Handle other OS
#endif
一个技巧是定义一个泛型函数并将其设置为适当的依赖于OS的函数(请记住,没有括号的函数代表函数的地址,而不是实际的函数调用),如下所示:
void RequestSomeKernelAction();
#ifdef WIN32
RequestSomeKernelAction = WindowsVersion;
#else
RequestSomeKernelAction = OtherOSFunction;
#endif
这就是说,您在头文件中看到的代码遵循这些相同的规则。如果我有以下头文件:
#ifndef SRC_INCLUDES_TEST_H
#define SRC_INCLUDES_TEST_H
int square(int a);
#endif /** SRC_INCLUDES_TEST_H */
我有这个main.c文件:
#define SRC_INCLUDES_TEST_H
#include "test.h"
int main()
{
int n = square(4);
}
此程序无法编译。 main.c将不知道square函数,因为虽然我确实包含了声明square
的头文件,但我的#define SRC_INCLUDES_TEST_H
语句告诉预处理器将所有头文件内容复制到main,除了那些在定义SRC_INCLUDES_TEST_H
的块中,即......没有。
这些预处理器命令可以嵌套,有几种,我强烈建议您查看,如果仅出于历史或教学原因。
我要说的最后一点是,虽然C预处理器有它的缺点,但它是一个强大的工具,事实上,第一个C ++编译器Bjarne Stroustroup写的基本上只是一个预处理器。