【C语言】程序环境和预处理

【C语言】程序环境和预处理

一、程序环境

1.程序的翻译环境

(一)、这个环境干什么
就是将原代码翻译成计算机可以看懂的语言,即机械语言
(二)、这个环境具体做了什么
将项目内的所有源代码通过进行单独编译,然后将编译后的得到的目标文件通过链接器进行连接,来生成可执行程序。
用图说的话就像这样:
翻译流程图

(1)编译

编译有三个环节,分为预编译编译汇编,并按该顺序依次执行:
预编译:就是将源代码的注释去掉,将头文件展开,将程序中有相关宏的地方进行替换,生成.i文件
编译:将具体代码进行词法,语法,语义分析和符号汇总,生成.s文件
汇编:将编译后的文件转换为二进制指令,并且将编译时得到的各种符号汇总符号表,而且符号表内只有全局符号(如函数名),生成.o文件,即目标文件

(2)链接

如上文所说,这个阶段,就是将个源文件所生成的目标文件,依靠链接器串联在一起,同时连接器将会引入标准C函数库中任何被源文件所用的函数,而且它可以搜索程序员个人的程序库,寻找想要的函数链接到程序中,除此之外,这个阶段主要的做的事就两个,一是合并段表,二是将符号表合并和重定位。

合并段表
目标文件的内部具体的信息实际上是会分区存放的,假使我们有一个名a.o的文件,它的信息可能是在文件中是这样存放的

a.o文件内的信息存放

我们在假设我们有个b.o的文件,其内的信息如a.o存放,将他们各段的信息合并,就是合并段表

符号表的合并和重定位
假如我们在一个项目里有如图这两个文件:
在这里插入图片描述
且我们将main()函数的地址设为0x30000000,Add函数的地址设为0x10000000.
执行test.c程序时,首先进入执行extern Add语句,发现这是一个函数声明,意思就是我只知道有Add这个函数,但是具体在哪里不知道,接着进入main函数,发现main函数在内存中的地址是0x30000000,记录下来,执行完test.c文件后,接着进入Add.c文件中,发现Add.c文件中有一个Add.c函数,地址是0x10000000,记录下来。
之后,将两个目标文件通过链接器合并,会将test.c和Add.c的地址合并,即
在这里插入图片描述
而重定位就是在合并之后,将新的符号表变为运行信息,不在使用旧的符号表,便是重定位

2.程序的执行环境

执行过程:
(一)程序必须载入到内存之中。在有操作系统的环境中:一般通过操作系统完成。而在独立环境中,必须手工操作,也可能是通过把可执行代码植入内存来完成。
(二)程序开始执行,之后调用main()函数。
(三)开始执行程序代码,这时候会使用一个运行时的堆栈,用于储存函数的局部变量和返回地址。程序同时也可以使用静态内存,存储于静态内存的变量在程序的整个执行过程中一直保存它的值。
(四)最后就是终止程序。正常终止main()函数;也可能以外终止(比如在执行过程中报错)。

二、预处理

1.预定义符号

	以下是语言自带的
	__FILE__   显示正在编译的源文件
	__LINE__   显示文件当前行数
	__DATE__  显示文件被编译日期
	__TIME__   显示文件被编译的时间
	__STDC__   如果编译器遵守ASNI C ,值为1,否则未定义
	部分符号的正确打开方式:
#include<stdio.h>
int main()
{
	printf("file:%s line:%d\n", __FILE__, __LINE__);
	printf("Date:%s\n", __DATE__);
	printf("time:%s\n", __TIME__);
	
	return 0;
}

2.#define

(一)#define定义的标识符

	语法:
		#define name stuff
	举个例子:
	#define MAX 100
	注:不建议在#define的**末尾**加;
	来个实例:
#define MAX 100
#include<stdio.h>
int main()
{
	int sum=0;
	int i=0;
	for(i=0;i<=MAX;i++)
	{
		sum+=i;
	}	
}

当你在尝试编译时,会发现它报错了。为什么?因为对于计算机而言,它是这样的

#include<stdio.h>
int main()
{
	int sum=0;
	int i=0;
	for(i=0;i<= 100;i++)
	{
		sum+=i;
	}	
}
这样错误就显而易见了。
为什么会这样?当你了解了#define的替换规则,你就悟了。

(二)#define所定义的宏

和上面的语法类似:
#define name(parment-list) stuff
其中parment-list为参数表,它有可能出现在stuff中。
举个栗子:

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

(三) #define 替换规则

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

(四) #和##

(1)#

如果想把一个宏参数变换成对应的字符串,可以试试 #。
举个例子:

#define PRINT(X, F)\  
printf("the value of " #X " is " F "\n",X)
#include<stdio.h>
int main()
{
	int i = 1;
	PRINT(i + 3,"%d");
	return 0;
}

其结果为:
在这里插入图片描述

(2)##

它可以将两个符号合成一个符号。
它允许宏定义从分离的文本片段创建标识符。
这里给了一个简单的例子

#define ADD_Print(a,b)\
 printf("%d", a##b)

#include<stdio.h>
int main()
{
	int a = 1;
	int b = 2;
	int ab = 12;
	ADD_Print(a,b);
	return 0;
}
这个宏因为是a##b, 所以在宏中,本质上是printf(“%d”, ab), 所以打印的是变量ab

(五) 带有副作用的宏参数

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

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

#include<stdio.h>
int main()
{
	int x = 5;
	int y = 8;
	int z = MAX(x++, y++);
	printf("x=%d y=%d z=%d\n", x, y, z);
	return 0;
}

结果会是x=5 y=9 z=8吗?而结果是却是
在这里插入图片描述

凭啥呢?因为#define的替换规则,MAX(x++, y++)实际上可以看成( (x++) > (y++) ? (x++) : (y++) )
该语句可以分为3部分
1.在判断大小时,是先判断,所以是x和y比,因为判断为价值,所以过后应执行 **:**后指令,
2.因为是判断x++和y++,比大小之后,均应自身+1,此时x=6,y=9,
3.因为是y++,所以z=9,后执行+1操作,即y=10。

(六)宏与函数的对比
如图:
在这里插入图片描述

3.undef

		将定义过的宏移除
		用法:
		#undef NAME

4.条件编译

在代码调试阶段,有的语句想要暂时遗弃,所以我们需要条件编译语句。

直接出语法
	1.
	#if 常量表达式
	...
		各种命令操作
	...
	#endif
	
	2.多个分支的条件编译
	#if 常量表达式
	...
		各种命令操作
	...
	#elif 常量表达式
	...
		各种命令操作
	...
	#else
 	...
		各种命令操作
	...
	#endif
	
	3.判断是否被定义
	#if  defined(symbol)
	#ifdef symbol
	#if  !defined(symbol)
	#ifndef symbol
	
	4.嵌套
	#if defined(name)
			#if  defined(symbol)
				各种命令操作
			#endif
	#endif

5.文件包含

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

(一)头文件被包含的方式

(1).本地文件包含
用法:
#include “filename”
查找策略:先在源文件所在目录下查找,在向库函数头一样在标准位置查找头文件。当然查不到就报错。
(2).库文件包含
用法:
#include<filename.h>
查找策略:直接去标准路径去查,查不到报错

(二)镶嵌文件包含

如果有这样的情况:
在这里插入图片描述

	comm.c和comm.h是公用模块
	test.h,test.c和test2.h都用公用模块,而test1.c又调用了test.h和test2.h,最后到test.exe中,实际上是拷贝了两份comm.c。
	那有没有办法解决这个问题呢?
	当然有可以试试上文说的条件编译,以上图为例,在comm.h加上
#ifndef _TEST_H_
#denfine _TEST_H_
//具体内容
#endif

或者直接在开头加上
#pragma once

写在后面

第一次写博客,而且是C语言初学者,可能本文有些是不严谨或者是不正确的,欢迎各位大佬在评论区指正。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值