程序环境和预处理(C语言)

1. 程序环境

当我们写完一个.c或者.h文件的时候,直接用鼠标点击执行,编译器就会自动帮我们生成一个.exe的可执行程序,这一切都是由编译器完成的,而这一过程分别存在于以下两个不同的环境中。

第一种是翻译环境,翻译环境中有两个步骤,编译+链接,在这个环境中源代码被转化为可执行的机器指令。(这一过程是将.c文件翻译成程序可以执行的.exe机器语言的文件)

第二种是执行环境,它用于实际执行代码。(这一过程是执行刚才翻译环境翻译好的二进制的.exe文件 )

在这里插入图片描述

1.1 翻译环境

当一个项目中有多个.c或者.h文件的时候,每个文件都会单独经过编译器的编译并生成所对应的目标文件.obj,编译器编译完成后将这些编译完成的目标文件经过连接器连接到一起从而生成可执行的.exe文件;因此翻译环境是由 编译+链接 两个步骤来完成的;

而编译本身又分为三个步骤:预编译->编译->汇编;
预编译:头文件的包含,注释的删除 ,#define 的翻译 然后生成一个.i文件
编译:语法分析,词法分析,语义分析,符号汇总 然后生成一个.s文件
汇编:把汇编代码转化成二进制的指令文件 Windows环境下生成.obj文件 Linux环境下生成.o文件

链接:形成符号表,合并段表,符号表的合并和符号表的重定位。编译的部分将一个或多个.c或.h文件生成多个目标文件(.o或.obj)后,链接的作用就是将他们合并起来并生成最终的可执行文件.exe

在这里插入图片描述

1.2 执行环境

程序的执行过程:

  1. 程序必须载入内存中。在有操作系统的环境中:一般这个操作由操作系统完成。在独立的环境中,程序的载入必须手动安排,也可能是通过可执行代码置入只读来完成。
  2. 程序的执行开始,然后就调用main函数
  3. 执行代码。这时候程序将使用一个运行的堆栈,存储函数的局部变量和返回地址。程序同时也可以使用静态内存,存储与静态内存中的变量在程序的整个执行过程一直保留他们的值。
  4. 终止程序。正常终止main函数,也有可能是意外终止

2.预处理

2.1 预定义符号

__FILE__   //进行编译的源文件
__LINE__   //文件当前的行号
__DATE__  //文件被编译的日期
__TIME__  //文件被编译的时间

这些预定义符号都是C语言内置的。
举例:

#include<stdio.h>
int main()
{
	int arr[] = { 1,2,3,4,5 };
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d----文件位置:%s, 行号:%d , 日期:%s , 时间:%s\n",
			arr[i], __FILE__, __LINE__, __DATE__, __TIME__);
	}
	return 0;
}

运行结果:
在这里插入图片描述

2.2 #define

2.2.1 #define定义标识符

举例:

#define MAX 100        //定义一个最大数MAX为100的标识符
#define CASE break;case    //定义一个新的CASE以防止忘记写break

注意:在用define定义标识符的时候不能在末尾加上 ; 因为编译器在编译的时候会把所定义的符号直接转换成所定义的值,举个例子:

#define MAX 100;
printf("%d",MAX);

这样编译就会报错,因为 100; 这个整体并不是整型,而100 是,因此在用define定义的时候不能加上;

2.2.2 #define定义宏

#define 机制包括了一个规定,允许把参数定义到文本中,这种实现通常成为宏(macro)或定义宏(define macro)
语法:
#define name(参数) 定义
举例:

#define SQURE(x) x*x     //定义一个求2次方的宏
int main()
{
	printf("%d\n",SQURE(10));
	return 0;
}

运行结果:
在这里插入图片描述
但是,因为编译操作对宏是直接进行替换的,因此,在定义宏和在对宏传参的时候一定要考虑到符号的优先级,例如同样的代码运行结果就不同:

//同样的代码不同的参数
#define SQURE(x) x*x     //定义一个求2次方的宏
int main()
{
	printf("%d\n",SQURE(5+5));
	return 0;
}

运行结果:
在这里插入图片描述
在SQURE宏的内部,编译结束后的实际代码应该是 5+5*5+5 因为 * 的优先级高于 + ,因此先乘后加,从未导致结果大不相同,想要解决这类问题,我们可以在宏的定义中直接加上(),例:

#define SQURE(x) (x)*(x)     //定义一个求2次方的宏
int main()
{
	printf("%d\n",SQURE(5+5));
	return 0;
}

运行结果:
在这里插入图片描述

2.3 带副作用的宏参数

当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那就有可能出现危险
举例:

#define MAX(a,b) ((a)>(b)?(a):(b))
int main()
{
	int a = 3;
	int b = 5;
	int c = MAX(a++,b++);

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

运行结果:
在这里插入图片描述
我们知道,在编译的时候宏所定义的内容会变成: ((a++)>(b++)?(a++):(b++));像这种会改变原参数值的宏,就是带有副作用的。

2.4宏和函数的对比

宏通常被应用于执行简单的运算
比如:

#define MAX(a,b) ((a)>(b)?(a):(b))

但是用函数就没有用宏更好,原因有二:

  1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。因此宏在程序规模和运行速度方面比函数更小更快
  2. 函数的参数必须声明为特定类型,而宏不用。;

当然,宏和函数相比也有欠缺的地方:

  1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
  2. 宏是无法调试的。
  3. 宏由于类型无关,也就不够严谨
  4. 宏可能会带来运算符优先级的问题,从而导致程序容易出错

因此在宏和函数中进行选择的时候,要根据实际情况和需要来判断。

2.3 #undef

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

#undef NAME
//如果现存的一个名字需要重新被定义,那么它的旧名字首先要被移除

2.4 条件编译

常见的条件编译指令:

  1. #if 常量表达式
    //···
    #endif
    注意:前面写了#if写完这个条件语句时一定要写上#endif
    例:
#define NUM 1
#if NUM
	//···
#endif
  1. 多个分支的条件编译
#if 常量表达式
	//···
#elif 常量表达式
	//···
#else
	//···
#endif
  1. 判断是否被定义
#ifdef symbol  //是否定义
#ifndef symbol //是否未定义
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值