C语言——程序的编译与执行、宏定义详解

        今天我来和说一说代码是如何转换为计算机所能识别的语言,程序运行过程的步骤和什么是宏,它又是如何被定义的?

目录

一.程序的翻译环境和执行环境

1.翻译与执行的来源

2.翻译环境

2.1编译阶段:

2.2链接阶段: 

二.执行环境

1.执行环境的步骤:

 三.预处理详解:

1.预定义符号

 2.#define定义标识符

        标识符声明:

3.#define 定义宏

                        #define name( parament-list ) stuff

4.宏定义的练习:

3. 嵌套宏定义

4. #define 替换规则

5.宏和函数的对比

6. 命名约定

7.#undef


一.程序的翻译环境和执行环境

1.翻译与执行的来源

        在ANSI C的任何一种实现中,存在两个不同的环境。 第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。 第2种是执行环境,它用于实际执行代码这两种环境的搭配使源程序test.c文件转换成为可执行程序.exe文件。

2.翻译环境

翻译环境会分为两个大阶段:编译阶段和链接阶段。  

        

2.1编译阶段:

编译阶段共需要执行3个步骤:1.预处理     2.编译      3.汇编

test.c

#include <stdio.h>
int main()
{
  int g_val=100;
 printf("%d\n", g_val);
 print("hello bit.\n");
 return 0;
}

1.预处理过程:

A.#include头文件的包含————>关键语句(处理)

B.删除注释————>使用空格代替注释。

C.#define宏定义符号的替换————>程序会将代码中的宏量替换为常数,,替换后会删除定义符号。

        在预处理阶段中,程序会将我们所写的代码文件(test.c )放入一个新的文件(test.i) 中,另外每次开头写的头文件(例如:#include<stdio.h>这种头文件中的所有内容)也会被程序放入test.i中;接下来就是删除注释,注释是程序员标注给自己看的,计算机只需要代码,所以会清理代码之外的东西,当然宏define也会被清理,define所代替的常量被分解直接放入代码中。 

2.编译过程:

        程序会把C代码翻译成汇编代码,然后在汇编代码中做语法分析、词法分析、语义分析、符号汇总(main、函数名、全局变量等)。test.i文件会转换为test.s文件。

3.汇编过程:

        程序会把做好的汇编代码转换为计算机能读懂的二进制指令,并且形成各段的符号表test.s文件——>test.obj文件。


2.2链接阶段: 

        当test.c编译后得到的文件为.obj目标文件,虽然目标文件的内容是本地代码,但是无法直接运行,原因就是:当前程序还处于未完成状态。故.obj文件是尚未完成的本地代码。所以一个或多个.obj文件会通过链接器和链接库最终转换为.exe文件。

        每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。 链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人 的程序库,将其需要的函数也链接到程序中。


二.执行环境

1.执行环境的步骤:

1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序 的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。

2. 程序的执行便开始。接着便调用main函数。 

3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回 地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程 一直保留他们的值。

4. 终止程序。正常终止main函数;也有可能是意外终止。


 三.预处理详解:

1.预定义符号

__FILE__      //进行编译的源文件

__LINE__     //文件当前的行号

__DATE__    //文件被编译的日期

__TIME__    //文件被编译的时间

 以上这些预处理符号可以用在记录日志上:

int main() {
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL) {
		perror("fopen");
		return -1;
	}
	fprintf(pf,"file:%s\tline:%d\tdate:%s\ttime:%s\n", __FILE__, __LINE__, __DATE__, __TIME__);

	fprintf(pf, "file:%s\tline:%d\tdate:%s\ttime:%s\n", __FILE__, __LINE__, __DATE__, __TIME__);

	fprintf(pf, "file:%s\tline:%d\tdate:%s\ttime:%s\n", __FILE__, __LINE__, __DATE__, __TIME__);

	fclose(pf);
	pf = NULL;
	return 0;
}

 2.#define定义标识符

        标识符声明:

语法: #define name stuff

 name为宏名称,stuff为宏名的值,值可以是任何类型的数据,可以是表达式 、语句等。

3.#define 定义宏

         定义:#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏或定义宏。宏定义的计算是程序在翻译环境中预处理阶段进行的! 下面是宏的申明方式:

                        #define name( parament-list ) stuff

        参数详解:         

        parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。

        注意: 参数列表的左括号必须与name紧邻。 如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。

                                隔了一个空格,就表明(X)是属于stuff部分的了。 


4.宏定义的练习:

1.

#define QUARE(X) X*X

int main() {
	int r1 = QUARE(6);
	printf("%d\n", r1);

	int r2 = QUARE(6 + 2);
	printf("%d\n", r2);

        由上述代码可知,QUARE(X) X*X 是宏定义,意为求X的平方,而r1被赋值被求QUARE(6),即6的平方,所以ri这条语句可以转换为:int r1=6*6;     r1==36;

       而r2这条语句通过表面可知是要求6+2的平方赋值给r2,但结果为:20  如下图:

原因是:int r2= 6+2*6+2;6+2*6+2等于6+12+2等于20。这是由于符号优先级导致,需要加括号,通过改进为下图代码:

	
#define QUARE(X) X*X
#define QUARE2(X) ((X)*(X))
int main() {
//改进1:
	int r3 = QUARE2(6 + 2);
	printf("%d\n", r3);

	//改进2:
	int r4 = QUARE((6 + 2));
	printf("%d\n", r4);

    return 0;
    }

        通过改进可以得到想要的结果:


练习2. 

#define DOUBLE(X) X+X

int main() {
	int q = DOUBLE(3 * 2);
	printf("%d\n", q);

    int q1 = 10 * DOUBLE(3 * 2);
	printf("%d\n", q1);

    return 0;
    }

        虽然这个宏对于q的运算则没那么强要求加括号,因为优先级会先算*号,再算+号,不影响计算;但对于q2则很需要括号,如代码所示,本是想计算10*DOUBLE,也就是10*(6+6)得120,但是没有括号就会先计算10*6+6得出66。

        结论:

        所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中 的操作符或邻近操作符之间不可预料的相互作用。

3. 嵌套宏定义

#define M 100
#define DOUBLE2(X) ((X)+(X))
int main() {
	int s = DOUBLE2(M+2);
	printf("%d\n", s);

    return 0;
    }

        上述代码是宏定义中套了一个宏,计算的过程也是从里到外替换符号://int s=((100+2)+(100+2))=204 。

        注:字符串中有宏没用!   系统认为双引号中的都只是字符而已,并不会理解为宏定义。


4. #define 替换规则

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

        1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先 被替换。

         2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。

         3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上 述处理过程。 

练习:


int main() {
	int a = 10;
	printf("the value of a is %d\n", a);

	int b = 20;
	printf("the value of b is %d\n", b);
	return 0;
}

        在上述代码中,建一个变量则需要printf,若建的多了会造成代码冗余, 这时大家想必会想到函数:

void Print(int n) {
	printf("the value of n is %d\n", n);
}

         函数虽然传了形参,但不能输出与两个printf一样的内容,且函数的类型和格式就限制死了,而宏定义则可以轻松解决:

#define PRINT2(N,FORMAT) printf("the value of "#N" is "FORMAT"\n",N)

int main() {
	int a = 10;
    PRINT(a,"%d");    

	int b = 20;
	PRINT(b,"%d");
    
    float c = 3.14;
	PRINT(c,"%f");

	return 0;
}

 调试结果:

     宏其实是可以无视数据类型的,只要有数据传进来,它就可以进行相应的计算;而函数做不到。


5.宏和函数的对比

代码1:

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

int max(int x, int y) {
	return x > y ? x : y;
}
int main() {
	int m = 10;
	int n = 15;
	int t = MAX(m, n);
	int t2 = max(m, n);
	return 0;
}

        上述代码中,既有宏计算三目运算表达式,也有用函数计算三目表达式,到底哪一种更加方便快捷?

        答案肯定是宏。首先介绍宏定义的时候说过宏定义的计算是程序在翻译环境中预处理阶段进行的!所以宏在代码预处理的过程中就计算完成,时间花费很短;而函数调用的执行流程需要先进行传参,函数实参接收,开始执行函数内部运算,最后还要将值返回,这一来二回的会花费很多时间,所以宏比函数更加具有优先权。

        而且,若m和n的值进行改变的话:

        函数返回的值会是3,而宏定义得出的值是3.95。 函数的参数必须声明为特定的类型, 所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以 用于>来比较的类型这在无形之中就产生了一种落差感,函数没有宏灵活多变!

        其实,宏也是一把双刃剑,有高光的时刻也会有低谷:

        1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。

        2. 宏是没法调试的 。

        3. 宏由于类型无关,也就不够严谨。

        4. 宏可能会带来运算符优先级的问题,导致程容易出现错。

所以总体说来,函数和宏各有千秋,于是我查阅资料总结出很多宏和函数的优缺点:


6. 命名约定

        一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。 那我们平时的一个习惯是:

把宏名全部大写

函数名不要全部大写


7.#undef

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

#define M 12345
int main() {
	int r = M;
	printf("%d\n", r);

#undef M
	int p = M;
	printf("%d\n", p);

	return 0;
}

    调试结果:

    r的值为12345,移除了宏,M便是未声明的变量,p变量使用会报错!!!

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橙予清的zzz~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值