编译与链接

本文详细解释了C源文件如何转化为.exe可执行程序的过程,涉及预处理、编译(包括命令行定义、宏和条件编译)、汇编以及链接(包括地址分配和重定位)等关键步骤,以及运行环境中的内存管理和程序执行流程。
摘要由CSDN通过智能技术生成

欢迎来到本期频道!

你是否好奇.c源文件是如何转变成.exe可执行程序的。

一:源文件到可执行程序的大致过程

在ANSI C的任何一种实现中,一定存在翻译环境和运行环境。

文件
.c源文件
.h头文件
.......
翻译环境
编译
链接
运行环境
.exe可执行程序
输出结果

二:编译与链接

在这里插入图片描述

1.编译

Ⅰ.预处理

① 命令行定义

许多c的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。

② #define 定义常量

定义标识符时,一般不在最后加; 因为容易出现问题。

#define Symbol  常量表达式/函数实现/关键字	//定义常量


//如果替换的内容较长,可以用续航符分段写。例如
#define  test   int a=1;\
	printf("%d",a)

预处理阶段,语句中的(不包含字符串)Symbol被替换成该符号后面的内容。

③ #define 定义宏

#define机制允许把参数替换到文本中,这种实现被称为宏或定义宏。

#define  name(parament-list)  stuff
//参数列表左括号必须紧邻name,否则就是定义常量。

宏的替换规则

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

2.替换文本随后被插入到程序中原来文本的位置。参数名被它们的值替换。

3.再次对结果文件扫描,查看是否由#define定义的符号,如果是,重复以上过程。

使用宏

#include<stdio.h>
#define WAY(x,y)	x*y+x/y		//定义宏
int main()
{
	printf("%d ", WAY(2, 1));		//1号
	printf("%d ", WAY(4-2, 2-1));		//2号
	int a = 2;
	int b = 0;
	printf("%d\n", WAY(a++, ++b));	//3号		带副作用的参数
	return 0;
}

代码结果:

4 1 5

观察:

我们使用宏时,觉得这和函数很相似,但是如果是函数的话,这三个值应该是一样的,这说明宏有区别于函数的特点。
1号结果是我们想要的。
2号不是,那该如何解决?上面规则中介绍到宏的本质是替换,所以推断出此处产生了优先级的问题,那加上括号便可以解决问题--> ((x)*(y)+(x)/(y))
3号是因为带副作用的参数所导致的难以预测的结果。

宏的运算符
#运算符—>字符串化


#define OUT(n,formate)	printf(#n"=" formate ,n)	//将n替换成a后,再把a转换成字符串
int main()											//即printf("a" "=" "%d" ,a)
{
	int a=1;
	OUT(a,"%d");
	return 0;
}

输出结果:

a=1

##运算符—>记号粘合

#include<stdio.h>
#define GENERIC_MAX(type)	\
	type 	type##_max(type x, type y)\
{\
	return x>y ? x : y; \
}
GENERIC_MAX(int);		//替换后是 int int_max(int x,int y) { return x>y?x:y } ;
GENERIC_MAX(float);		//如果没有##,这里的int_max就是type_max   所以##起到了记号粘合的作用
int main()
{
	int ret1 = int_max(3, 5);
	float ret2 = float_max(1.02f, 3.08f);
	printf("%d %.2f", ret1, ret2);
	return 0;
}

运行结果:

5 3.08

宏和函数对比

属性#define定义宏函数
代码长度每次使用时,宏代码都要被替换,可能会大幅度增加代码长度每次使用时,只需调用同一份函数代码
操作符优先级由于宏是文本替换,所以临近操作符的优先级可能会造成难以预测的后果,所以加上括号以达预期效果函数调用时,只将参数值传递,结果容易预测
参数类型宏的参数与类型无关,操作合法,即可使用任意类型函数传参与类型有关,对于一些相同的任务,也要定义不同的函数。
带副作用的参数副作用参数可能会被替换到宏体多次,将造成不可预料的后果函数传参只传参数值,结果易控制
执行速度更快有函数调用和返回的开销,慢些
递归不可以可以
调试不方便可逐语句

强调一下
宏:参数可以是类型
函数:可递归

④ 命名约定

由于函数和宏的使用语法很相似,所以为了区别,我们通常把宏名全大写,函数名不全大写。

⑤ 预定义符号

c语言设置了一些预定义符号,可直接使用。
预定义符号含义
__FILE__进行编译的源文件名
__LINE__文件当前所在行号
__DATE__文件编译时的日期
__TIME__文件编译时的时间
__STDC__如果遵循ANSI C,值为1,否则未定义

⑥ 条件编译

条件编译使得我们可以对一条语句(一组语句)选择是否编译。

常见的条件编译指令

#if  常量表达式
	//表达式由预处理器求值,表达式为真,包含的语句参与编译
#elif  常量表达式
	//
#else
	//
#endif


#if defined(符号)
	//符号定义了,语句参与编译,与该符号的值无关
#endif
//或者
#ifdef  符号
	//
#endif


#if !defined(符号)
	//符号没有定义,语句参与编译,与符号值无关
#endif
//或者
#ifndef 符号
	//
#endif

⑦ 头文件包含

本地文件包含
#include"filename.h"

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

库文件包含
#include<filename.h>

查找策略直接标准路径下查找,找不到提示编译错误。

所以为了提高查找效率,本地头文件用" ",库函数头文件用< >.

文件多次包含怎么办?
----条件编译。

#ifndef FILENAME_H
#define FILENAME_H
	//...头文件内容
#endif

Ⅱ. 编译

编译的目的是把c语言转换成汇编语言
编译的主要过程
词法分析
语法分析
语义分析及优化,该阶段会报告错误的语法信息

Ⅲ.汇编

汇编目的是将汇编语言文件转化成二进制文件

2.链接

链接是一个复杂的过程,链接时需要把一堆文件链接在一起才生成可执行程序。
链接解决的是一个项目中多文件,多模块互相调用的问题。
链接主要过程
地址和空间分配
符号决议
重定位 (地址的修正过程)
… …

三:运行环境

1.程序必须载入内存中。

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

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

3.开始执行程序代码。

这时将使用一个运行时堆栈,存储函数的局部变量和返回地址. 同时也可以使用静态内存,存储于静态内存中的变量在程序的整个执行过程一直保留它们的值.

4.终止程序。

正常终止main函数;意外终止.
  • 27
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值