本文主要介绍C语言预处理预编译命令和宏定义的详细说明,包括运行环境的命名约定条件和#under的基本详细说明等。有需要的朋友可以参考一下。
目录
程序翻译环境和执行环境翻译环境:编译环节1详解。编译-预处理/预编译test.c - test.i2编译-编译test.i - test.s3编译-汇编test.s - test.obj4链接test.obj-test.exe运行时环境预处理/预编译细节#定义定义标识符#和# # #。命令行的命名约定定义了编译的条件。常见的条件编译指令文件包含offsetof(宏类型,成员名)偏移量模拟实现。c源程序-编译-链接- exe -运行-运行-。
程序翻译环境和执行环境
翻译环境:将源代码转换成可执行的机器指令(二进制代码)。
执行环境:用于实际执行代码。
翻译环境:详解编译+链接
1.组成程序的每个源文件都通过编译过程转换成目标代码。
2.每个目标文件都被一个链接器连接在一起,形成一个完整的可执行程序。
3.链接器还会在标准C函数库中引入程序使用的任何函数,它可以搜索程序员的个人库,将需要的函数链接到程序上。
Extern在外部文件中声明函数。
1. 编译 — 预处理/预编译 test.c ---- test.i
文本操作
包含#include头文件
删除:用空格替换注释。
#定义替换,所以宏无法调试。
……
2. 编译 — 编译 test.i ---- test.s
将C语言代码翻译成汇编代码
语法分析
词汇分析
语义分析
符号摘要
3. 编译 — 汇编 test.s ---- test.obj
将汇编代码转换成二进制代码(指令)。
形成一个符号表。(符号地址)
4. 链接 test.obj ---- test.exe
合并段表
符号的合并和重新定位
运行环境
1.程序必须装入内存。在有操作系统的环境中:一般来说,这是由操作系统来完成的。在独立的环境中,程序的装入必须手动安排,或者可以通过将可执行代码放入只读存储器来完成。
2.程序开始执行。然后调用主函数。
3.开始执行程序代码。此时,程序将使用一个运行时堆栈来存储局部变量和函数的返回地址。同时,程序也可以使用静态内存,静态内存中存储的变量在程序的整个执行过程中保持其值。
4.终止程序。正常终止主函数;也可能是意外终止。
预处理/预编译详解
预定义符号
原始符号
__FILE__ //用于编译的源文件
__LINE__ //文件的当前行号
__DATE__ //文件编译的日期
__TIME__ //文件编译的时间
__STDC__ //如果编译器遵循ANSI C,则其值为1,否则未定义
app应用
printf('数据:%s\n时间:%s ',__日期__,_ _时间_ _);
输出
数据:2021年7月13日
时间:15时13分54秒
#define 定义标识符
宏伟的
与定义宏不同,宏有参数。
下面是宏的声明方式:
#定义名称(参数列表)内容
Parament-list是由逗号分隔的符号列表,可能出现在stuff中。
参数的左括号必须紧邻name。如果两者之间有任何差距,参数列表将被解释为stuf的一部分。
例如
#定义正方形(X) (X)*(X)
int main()
{
int ret=SQUARE(5);
返回0;
}
宏参数被替换,而不是被传递。
定义宏时不要吝啬括号。
#和##
#的作用
使用#将宏参数转换为相应的字符串。
将参数插入字符串。
# define PRINT(X)printf(' # X '的值是%d\n ',X)
int main()
{
int a=10
int b=20
打印(一份);
打印(b);
返回0;
}
输出
a的值是10
b的值是20
##的作用
# #它两边的符号可以合并成一个符号,允许宏定义从分离的文本片段创建标识符。
#定义CAT(X,Y) X##Y
int main()
{
int class84=2021
printf('%d\n ',CAT(class,84));
}
输出
2021
有副作用的宏参数
#定义MAX(a,b) ( (a) (b)?(a) : (b))
.
x=5;
y=8;
z=MAX(x,y);
printf('x=%d y=%d z=%d\n ',x,y,z);
//输出的结果是什么?
这里,我们需要知道预处理程序处理的结果是什么:
z=( (x ) (y)?(x):(y);
输出结果
x=6 y=10 z=9
宏功能和宏功能的比较
对于上面提到的宏,也可以用函数来实现其功能。
使用宏的优点:
1.用于调用函数和从函数返回的代码可能比实际执行这个小的计算工作花费更多的时间,所以宏在程序规模和速度上要优于函数。
2.函数的参数必须声明为特定类型,因此函数只能用于具有适当类型的表达式。相反,这个宏可以用于整型、长整型、浮点数等。宏是独立于类型的。
使用宏的缺点:
1.每次调用宏时,都会在程序中插入一个由宏定义的代码,这可能会大大增加代码的长度,除非宏很短。
2.无法调试宏。在预编译(预处理)阶段,# define已经被取代,不再是宏。
3.宏不够严谨,因为它们与类型无关。
3.宏可能会引起操作符优先级的问题,更容易导致程序错误。
内嵌内嵌函数
命名约定
函数类似于宏语法,语言本身无法帮助我们区分。大写所有宏名,而不是所有函数名。
#undef删除宏定义
该指令用于删除宏定义。
如果需要重新定义现有名称,则必须首先删除旧名称。
#undef名称
命令行定义
许多C编译器提供了在命令行定义符号来启动编译过程的能力。例如,当我们想要根据源文件编译不同版本的程序时,这个特性就很有用。假设在一个程序中声明了一个一定长度的数组。如果机器内存有限,我们需要一个小数组,但是另一个机器内存是大写的。我们需要一个可以大写的数组。
条件编译
#定义调试
#ifdef调试
#endif
常见的条件编译指令
#if常量表达式
//.
#endif
示例:如果为真,则参与编译,如果为假(0),则不参与编译。
#如果1
printf('balabala . ');
#endif
第二,有条件地编译多个分支
#if常量表达式
//.
#elif常量表达式
//.
#否则
//.
#endif
例如
#如果1==1
#elif 2==1
#否则
#endif
第三,确定是否定义。
#如果已定义(符号)
#ifdef符号
#如果!已定义(符号)
#ifndef符号
四。嵌套指令
#如果已定义(OS_UNIX)
#ifdef选项1
UNIX _ version _ option 1();
#endif
#ifdef选项2
unix版本选项2();
#endif
#elif已定义(OS_MSDOS)
#ifdef选项2
msdos _ version _ option 2();
#endif
#endif
文件包含
我们已经知道#include指令可以编译另一个文件。就像它实际出现在#include指令所在的位置一样。这种替换方法很简单:预处理器首先删除这条指令,并用包含文件的内容替换它。如果这样的源文件被包含10次,那么它实际上会被编译10次。
如何包含头文件:
1.本地文件包含:#include 'Filename '
搜索策略:先在源文件所在的目录中搜索。如果没有找到头文件,编译器会像库函数头文件一样在标准位置查找头文件。如果找不到,会提示编译错误。
2.库文件包含:# includeFilename.h。
搜索策略:直接搜索头文件到标准路径,如果找不到就返回错误信息。
有没有可能说库文件也可以以“”的形式包含?
答案是可以,可以。但是这种方式搜索的效率较低,当然也不容易区分是库文件还是本地文件。
Wwww以为她经常反复包容,留下遗憾的眼泪~ ~
出现嵌套文件。解决方法:条件编译。
在每个头文件的开头,它的内容是:
#ifndef __TEST__H__
#定义__测试__H__
//头文件的内容
# endif//_ _测试__H__
或者
#杂注一次
可以避免头文件的重复引入。
总结一下:预处理阶段的预处理指令:条件编译指令/# include/# define/# error/# pragma/.
offsetof(宏类型,成员名字)偏移量模拟实现
#包含stdio.h
#包含stdlib.h
#包含stddef.h
结构S
{
char c1
int a;
char c2
};
#define OFFSETOF(结构名,成员名)(int)(((结构名*)0)-成员名)
int main()
{
printf('%d\n ',OFFSETOF(struct S,C1));
printf('%d\n ',OFFSETOF(struct S,a));
printf('%d\n ',OFFSETOF(struct S,C2));
返回0;
}
以上是C语言预处理预编译命令和宏定义的详细内容。关于C语言预处理预编译命令和宏的更多信息,请关注我们的其他相关文章!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。