c语言中编译过程与预处理

这篇具有很好参考价值的文章主要介绍了c语言中编译过程与预处理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

一、c语言的编译与链接

1、编译与链接概述

在c语言的一个项目中会有很多.c文件,这些.c文件就是源文件,组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code),即每个源文件都转换为对应的目标文件,即.obj文件。
然后这些目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。
链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中。
编译器将这些存储代码的源文件编译成存储机器可以识别的二进制指令的目标文件,然后链接器将这些目标文件链接起来,因为test.c中使用了add.c和sub.c中定义的函数,所以链接这一步就是将test.c中使用的这些函数链接起来。然后才会形成一个可执行的程序。
c语言中编译过程与预处理,c语言,笔记

2、编译与链接详解

源文件test.c和add.c在编译过程中,先进行预编译,在预编译阶段会将代码中的注释都去掉,然后像#define定义的标识符和宏等都会被替代掉,#include定义的头文件包含也会被引入其中,即test.i和add.i中有的是预处理过后的代码。可以看到test.c中的一些代码和注释在test.i中已经没有了。

c语言中编译过程与预处理,c语言,笔记
在经过预处理后,下一步就是将生成的test.i文件进行编译了,该处理会将c语言代码翻译成汇编代码。并且会进行语法分析、词法分析、语义分析和符号汇总等。
c语言中编译过程与预处理,c语言,笔记
最后编译器会将编译形成的test.cod汇编文件经过汇编处理变为机器可以识别的二进制指令。然后还会形成符号表。
c语言中编译过程与预处理,c语言,笔记
然后就是链接器将生成的目标文件test.obj和add.obj进行符号表的合并与重定位,还有段表的合并。
test.cod在汇编后会将全局的一些函数等形成符号表,add.cod在汇编后也会将全局的一些函数等形成符号表,当在链接时,因为test.obj和add.obj的符号表中都给Add符号申请了地址,但是经过检查后发现test.obj里面Add的地址无用,所以会合并符号表,即当程序要调用Add函数时,就去0x 500地址去找该函数。
然后链接器将这些目标文件和该程序引用的库里面的一些文件都链接到一起,形成可执行程序test.exe。
c语言中编译过程与预处理,c语言,笔记

c语言中编译过程与预处理,c语言,笔记

二、c语言预处理

1.c语言中内置的预定义符号

这些预定义符号都是c语言内置的,可以直接使用这些符号来打印我们想要的信息。
c语言中编译过程与预处理,c语言,笔记
c语言中编译过程与预处理,c语言,笔记
使用这些预定义符号就可以得到当前语句执行的相关信息。
c语言中编译过程与预处理,c语言,笔记

2、#define定义标识符

除了上面的预定义符号外,c语言也支持使用#define来定义标识符。
例如定义MAX 替代 100,则在下面的程序中如果使用了MAX,将都会在预处理阶段被替换为100.并且#define可以除了可以定义数字外,还可以定义字符、类型、代码等。

#define MAX 100
#define STR "abcdef"
#define FAC fac()  //调用函数
//如果定义的语句过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)
#define DEBUG_PRINT printf("file:%s line:%d" \
							"date:%s  time:%s\n",\
							__FILE__, __LINE__,\
							__DATE__,__TIME__)

void fac()
{
	printf("hh\n");
}
int main()
{
	int a = MAX;
	printf("%d\n", a);
	printf("%d\n", MAX);
	FAC;
	DEBUG_PRINT;
	return 0;
}

可以看到我们在test.c中的注释和#define定义的标识符都在预处理阶段被去掉了,而且#define定义的标识符就是将这些标识符直接替换成了后面的语句。所以在使用#define定义标识符后,语句后边不用加上 ; 符,因为替换时会将 ; 符也加上,这样语句就会有两个 ; ; 语句结束符。
c语言中编译过程与预处理,c语言,笔记

3、#define定义宏

#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(definemacro)。
#define name( parament-list ) stuff 其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。
注意: 参数列表的左括号必须与name紧邻。 如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。
在定义宏时应该尽可能多的使用括号,以避免出现下面的情况。
c语言中编译过程与预处理,c语言,笔记
所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。

#define SQUARE(x) ((x)*(x)) 
#define DOUBLE(x) ((x)+(x))

int main()
{
	int a = 9;
	int r = SQUARE(a);  //81
	//经过预处理后,等价于 int r = ((a) * (a));
	printf("%d\n", r);

	int z = SQUARE(a + 1);  //100
	//经过预处理后,等价与 int z = ((a + 1) * (a + 1));
	printf("%d\n", z);

	int ret = 3 * DOUBLE(100); //600
	//经过预处理后,等价于 int ret = 3 * ((100) + (100))
	printf("%d\n", ret);


	return 0;
}

4、#define 替换规则

在程序中扩展#define定义符号和宏时,需要涉及几个步骤。

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
  2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
    注意:
  4. 宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。
  5. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
#define ADD(x,y) ((x)+(y))
#define MAX 100

int main()
{
	//会先将MAX替换为100,然后再将ADD(2,100)替换为 ((2)+(100))
	int ret = ADD(2, MAX);
	printf("%d\n", ret);

	//字符串里面的MAX不会被替换为100
	printf("MAX = %d\n", MAX);
	return 0;
}

5、在使用#define定义宏时,#和##的作用

使用 # ,把一个宏参数变成对应的字符串。

//宏可以做到这样的操作
//参数前面加上#就表示直接替换为N这个字符,而不是N的值
#define PRINT(N,format) printf(#N" = "format"\n",N)

//函数不能将n改变为其他字符
void print(int n)
{
	printf("n = \n", n);
}
int main()
{
	int a = 10;
	double b = 3.14;
	//当我们想要输出这样的语句时,函数做不到,但是宏可以做到
	/*printf("a = %d\n", a);
	printf("b = %lf\n", b);*/
	PRINT(a,"%d");
	PRINT(b,"%lf");

	return 0;
}

##可以把位于它两边的符号合成一个符号。 它允许宏定义从分离的文本片段创建标识符。

#define CAT(name,num) name##num

int main()
{
	int class101 = 101;
	printf("%d\n", CAT(class, 101));
	//在预处理时,会变为 printf("%d\n", class101)
	//##可以把位于它两边的符号合成一个符号。 它允许宏定义从分离的文本片段创建标识符。

	return 0;
}

6、带副作用的宏参数

当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
例如

x+1;//不带副作用
x++;//带有副作用

即在宏执行的过程中,参数的值会发生变化。

#define MAX(x,y) ((x)>(y)?(x):(y))

int main()
{
	int a = 5;
	int b = 8;
	int c = MAX(a++, b++);
	//在预处理后变为
	//int c = ((a++) > (b++) ? (a++) : (b++));

	printf("%d\n", a);
	printf("%d\n", b);
	printf("%d\n", c);

	return 0;
}

7、宏和函数对比

宏通常被应用于执行简单的运算。比如在两个数中找出较大的一个。
#define MAX(a, b) ((a)>(b)?(a):(b))
那为什么不用函数来完成这个任务? 原因有二:
1、用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
2、更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的。
当然和宏相比函数也有劣势的地方:
1、 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
2、 宏是没法调试的。
3、宏由于类型无关,也就不够严谨。
4、宏可能会带来运算符优先级的问题,导致程容易出现错。

宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。

#define MALLOC(num,type) (type*)malloc(num*sizeof(type))

int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	int* p2 = MALLOC(10, int);
	//int* p2 = (int*)malloc(10 * sizeof(int));
	return 0;
}

命名约定: 把宏名全部大写 函数名不要全部大写。

8、#undef

这条指令用于移除一个宏定义。

#define MAX 100
int main()
{
	int a = MAX;
	printf("%d\n", a);
	printf("%d\n", MAX);
#undef MAX 
	//此时MAX已经不再为定义的标识符
	printf("%d\n", MAX);

	return 0;

}

三、条件编译

1、常见的条件编译指令

在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。
1、#if

#if 常量表达式
 //... 
#endif 
//常量表达式由预处理器求值。
如:
#define __ DEBUG __ 1 
#if __ DEBUG __ 
 //.. 
#endif 
int main()
{
	int arr[10] = { 0 };
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		arr[i] = i + 1;
//如果#if 后面的常量表达式为真,则里面的语句会参与编译
#if 1
		printf("%d ", arr[i]);
#endif
	}

	return 0;
}

2.多个分支的条件编译

 //... 
#elif 常量表达式
 //... 
#else 
 //... 
#endif 
#define NUM 1
int main()
{
	//只有满足条件的语句才会编译,而不满足条件的语句在预处理阶段就被去除了
#if NUM==1
	printf("hehe\n");
#elif NUM==2
	printf("haha\n");
#else
	printf("heihei\n");
#endif

	return 0;
}

3.判断是否被定义

#if defined(symbol) 
#ifdef symbol 
#if !defined(symbol) 
#ifndef symbol 
#define MAX 0
int main()
{
	//如果使用#define定义了MAX标识符就为真
#if defined(MAX)
	printf("defined\n");
#endif

#if !defined(MAX)
	printf("!defined\n");
#endif

//下面两组和上面两组等价
#ifdef MAX
	printf("ifdef\n");
#endif

#ifndef MAX
	printf("ifndef\n");
#endif

	return 0;

}

4.嵌套指令

#if defined(OS_UNIX) 
 #ifdef OPTION1 
 unix_version_option1(); 
 #endif 
 #ifdef OPTION2 
 unix_version_option2(); 
 #endif 
#elif defined(OS_MSDOS) 
 #ifdef OPTION2 
 msdos_version_option2(); 
 #endif 
#endif 
#define OPTION1 1

#define FAC1 1
#define FAC2 2
#define FAC3 3

int main()
{
#if defined(OPTION1)
	#ifdef FAC1
	printf("FAC1");
    #endif
#else 
	#ifdef FAC2
	printf("FAC2");
	#endif
#endif



	return 0;
}

四、文件包含

我们已经知道, #include 指令可以使另外一个文件被编译。就像它实际出现于 #include 指令的地方一样。这种替换的方式很简单: 预处理器先删除这条指令,并用包含文件的内容替换。 这样一个源文件被包含10次,那就实际被编译10次。

1、头文件被包含的方式

本地文件包含

#include "filename" 

查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。 如果找不到就提示编译错误。
库文件包含

#include <filename.h>

查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。
由上可知对于库文件,也可以用" "包含,但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。
嵌套文件包含
comm.h和comm.c是公共模块。 test1.h和test1.c使用了公共模块。 test2.h和test2.c使用了公共模块。 test.h和test.c使用了test1模块和test2模块。 这样最终程序中就会出现两份comm.h的内容。这样就造成了文件内容的重复。此时我们使用条件编译就可以解决这个问题
c语言中编译过程与预处理,c语言,笔记
每个头文件的开头写

#ifndef __TEST_H__ 
#define __TEST_H__ 
//头文件的内容
#endif //__TEST_H__ 

或者文章来源地址https://www.toymoban.com/news/detail-672373.html

#pragma once 

到了这里,关于c语言中编译过程与预处理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C语言深入】深入理解程序的预处理过程

    我们平时所写的每一个.c文件都会经过编译和连接的过程之后才会形成一个可执行程序: 今天我们就来详细的看看编译和连接这两个过程的具体细节。 程序的翻译环境与执行环境 在ANSI C的任何一种实现中,存在两个不同的环境。 第1种是翻译环境,在这个环境中源代码被转换

    2023年04月08日
    浏览(46)
  • 【c语言】详解c语言#预处理期过程 | 宏定义前言

    c语言系列专栏: c语言之路重点知识整合   创作不易,本篇文章如果帮助到了你,还请点赞支持一下♡𖥦)!!  主页专栏有更多知识,如有疑问欢迎大家指正讨论,共同进步! 给大家跳段街舞感谢支持!ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ 代码编译到执

    2024年02月01日
    浏览(52)
  • 初始C语言最后一章《编译、链接与预处理详解》

    感谢老铁们的陪伴和支持,初始C语言专栏在本章内容也是要结束了,这创作一路下来也是很不容易,如果大家对 Java 后端开发感兴趣,欢迎各位老铁来我的Java专栏!当然了,我也会更新几章C语言实现简单的数据结构!不过由于我是Java 技术栈的,所以如果以后有机会学习C

    2024年04月16日
    浏览(43)
  • 程序环境和预处理(含C语言程序的编译+链接)--2

    文章前言: 上章我们把      程序的翻译环境     程序的执行环境   C语言程序的编译+链接     预定义符号介绍    预处理指令   #define    宏和函数的对比     预处理操作符    #和##的介绍   的相关知识进行了梳理讲解,接下来被把剩余知识    命令定义     预处

    2024年02月14日
    浏览(56)
  • 015+limou+C语言深入知识——(7)编译环境和运行环境以及预处理指令

    在这个环境中,源代码被转化为可执行的机器指令(二进制指令) 单文件简易版本 多文件简易版本 编译链接详细版本 VS2022集成IDE(windows下)的编译器叫cl.exe,链接器叫link.exe gcc编译器(windows下)的几个有关编译环境的命令 (1)符号表会把全局变量和具有外部链接的函数

    2023年04月11日
    浏览(42)
  • C语言-程序环境和预处理(2)--带副作用的宏参数,宏与函数的对比,#undef,条件编译,文件包含

    上一篇文章–《C语言-程序环境和预处理(1)》讲述了程序的翻译环境和执行环境,编译、连接,预定义符号,#define,#符号和##符号的相关知识。 链接: 《C语言-程序环境和预处理(1)》 本篇文章,讲述带副作用的宏参数,宏与函数的对比,#undef,条件编译,文件包含的相

    2024年02月08日
    浏览(64)
  • 编译预处理:#if

    #if expression … #elif … #end expression 是整数常量比较的表达式,例如: defined表达式,例如 defined AAA, 或者 defined(AAA), 如果AAA是一个宏定义,return true,否则,return false; 单个整数,例如:1/10/100/0, 非零为true,零为false; 整数比较,例如:1 == 1为true, 0 == 0为ture, 1 2为false; 单个

    2023年04月14日
    浏览(71)
  • 【编译、链接、装载一】预处理、编译、汇编、链接

    简介 对于平常的应用程序开发,我们很少需要关注编译和链接过程,因为通常的开发环境都是流行的集成开发环境(IDE),比如Visual Studio等。这样的IDE一般都将编译和链接的过程一步完成,通常将这种编译和链接合并到一起的过程称为构建(Build)。即使使用命令行来编译一

    2024年02月07日
    浏览(47)
  • C/C++预处理过程

    目录 前言: 1. 预定义符号 2. #define定义常量 3. #define定义宏 4. 带有副作用的宏参数 5.  宏替换的规则 6. 宏和函数的对比 7. #和## 8.  命名约定 9.  #undef 10.  命令行定义 11.  条件编译 12.  头文件的包含 13.  其他预处理指令 总结:。 1. 预定义符号 c语言定义了一些预处理的

    2024年04月26日
    浏览(54)
  • 预处理/预编译详解(C/C++)

            在上一篇的bolg中的编译与链接中提到过 预处理 ,但只是较为简单的讲解,本篇将会对预处理进行详细的讲解。         其中在预处理中很重要的一个一个知识点是#define定义常量与宏,还区分了宏与函数的区别,以及#和##符号,还涉及条件编译头文件的包含等等。

    2024年01月17日
    浏览(55)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包