gpt4 book ai didi

c - 在C主体中包含头文件的机制

转载 作者:行者123 更新时间:2023-11-30 20:19:36 25 4
gpt4 key购买 nike

对于由C开发的大型软件,我们首先在一个单独的头文件(例如myfun.h)中声明所有自定义函数。之后,一旦编写了使用main.c中列出的功能的代码(例如myfun.h),就必须#include "myfun.h"。我想知道它是如何工作的,因为即使我在主体之前包括在头文件中声明的函数名称,代码也无法在main.c中看到函数的详细信息。我想它将搜索库以获取功能详细信息...对吗?

最佳答案

当您说“它将在库中搜索函数的详细信息”时,您虽然相距不远,但这并不完全正确。函数声明,即函数原型仅包含足够的信息,供编译器执行以下两项操作:


首先,编译器会将函数注册为已知的标识符,这样它就知道您在调用函数时所要处理的事情,这与带有括号的随机字母字符串相对(对于编译器,它们本质上是相同的,没有函数原型-错误)。
其次,编译器使用函数原型检查代码正确性。在这种意义上的正确性意味着函数调用将在原型和类型上都与原型匹配。换句话说,对int square(int a, int b);的函数调用将具有两个参数,均为整数。


但是,该程序不会“搜索库”。不带括号的函数名称不是函数调用,而是函数的地址。因此,当您调用一个函数时,处理器将跳转到该函数的内存位置。 (这假定该函数尚未内联。)

但是此功能在哪里?这取决于。如果您在同一模块中编写函数,即,将.c文件编译为与main.c文件链接为单个可执行文件的对象,则该函数的位置将位于可执行文件。换句话说,它与主函数的入口点仅有一点偏移。在大型项目中,此偏移量不会太小,但会比单独对象的偏移量短。

话虽如此,如果您将这个假设函数编译成一个DLL,然后从主程序中调用该DLL,则该函数的地址将通过以下两种方式之一确定:


您将生成一个.lib / .a吗? (取决于您使用的是Windows还是Linux)文件,其中包含函数声明的和地址,或者:
您将使用运行时链接,当主程序将.dll / .so加载到其地址空间时,该程序将在其中计算函数地址。首先,它将确定将其加载到何处。您可以将DLL设置为具有首选偏移量,以优化加载时间。否则,库将从可用的第一个段开始加载,并且任何其他库将需要使用此新地址重新计算其功能地址,这会影响初始加载时间。但是,一旦将它们加载到程序的内存中,此后就不会对性能造成任何影响。




回到预处理器,注意两点很重要。首先,它在任何编译发生之前运行。这个很重要。由于在预处理程序执行操作时并未真正“编译”程序,因此宏不是类型安全的。 (插入Haskell关于C“类型安全”的笑话)这就是为什么您不-或不应该-看到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);
}


该程序将无法编译。 Square函数对main.c来说是未知的,因为虽然我确实包括了声明了 square的头文件,但是我的 #define SRC_INCLUDES_TEST_H语句告诉预处理器将所有头文件的内容复制到main上,但块中的内容除外。 SRC_INCLUDES_TEST_H被定义,即..什么都没有。

这些预处理器命令可以嵌套,有几个,我强烈建议您查找它们,仅出于历史或教学上的原因。

我要说的最后一点是,尽管C预处理器有故障,但它在右手是一个功能强大的工具,实际上,第一个C ++编译器Bjarne Stroustroup实质上只是一个预处理器。

关于c - 在C主体中包含头文件的机制,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49635140/

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