C语言之编译链接

目录

一、翻译环境和运行环境

二、预编译

1.预定义符号

2.#define

3.头文件的包含

三、翻译环境

1. 预编译

2.编译

3.汇编

4.链接

一、翻译环境和运行环境

在ANSI C的任何⼀种实现中,存在两个不同的环境

第一种是翻译环境,在这个环境中,源代码被转换为可执行的二进制指令。翻译环境即我们日常使用编译器,将一个 " xxx.c " 的文件最终变成一个 " xxx.exe " 的可执行文件的一个过程。

第二种是运行环境,它用于实际执行代码。运行环境一般是由操作系统对 " xxx.exe " 可执行文件进行解析执行的结果。

二、预编译

1.预定义符号

C语言设置了⼀些预定义符号,可以直接使用,预定义符号也是在预处理期间处理的。

__FILE__ //进⾏编译的源⽂件 
__LINE__ //⽂件当前的⾏号 
__DATE__ //⽂件被编译的⽇期 
__TIME__ //⽂件被编译的时间 
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义 

2.#define

(1)#define定义常量

在define定义标识符的时候,不要在最后加上;

#define MAX 1000

if(condition)
 max = MAX;
else
 max = 0;

加了分号后,if和else之间就是2条语句,而没有大括号的时候if后边只能有一条语句。这⾥会出现语法错误。 

(2)#define定义宏

#include <stdio.h>
#define MAX(x,y) (x>y?x:y)

int main() {
	int a = 1;
	int b = 5;
	int E = MAX(a, b);
	// 预处理后变成 int E = (1>5?1:5);
	printf("%d\n", E); 
	return 0;
}

(2.1)宏的陷阱 

#include <stdio.h>
#define SQ(x) x*x

int main() {

	int a = 6;
	int e1 = SQ(a);
	// 预处理后:e1 = 6*6;  //36

	int e2 = SQ(a + 1);
	// 预处理后:int e2 = 6+1*6+1;  //8
	printf("%d\n", result1 );
	printf("%d\n", result2);
	return 0;
}

宏带来了运算符优先问题。由于 #define 在定义宏的时候,是直接对参数进行替换的。所以我们第二个预期为 " 49" 的结果,最终变成了 8.

#include <stdio.h>
#define SQ(x) (x)*(x)

int main() {

	int a = 6;
	int e1 = SQ(a);
	// 预处理后:e1 = (6)*(6);  //36

	int e2 = SQ(a + 1);
	// 预处理后:int e2 = (6+1)*(6+1);  //49
	printf("%d\n", result1 );
	printf("%d\n", result2);
	return 0;
}

可以利用加括号的方式,避免掉入宏的陷阱。解决优先级的运算符的问题 

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

#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
int main()
{
  x = 5;
  y = 8;
  z = MAX(x++, y++);
  printf("x=%d y=%d z=%d\n", x, y, z);
}
z = ( (x++) > (y++) ? (x++) : (y++));
//x=6 y=10 z=9

(3)#define 定义宏和函数的区别

(4)#undef

#undef 用于移除一个宏定义,在err的上一行,就是移除了 MAX 这个宏。之后再使用的时,就会报错

#include <stdio.h>
#define MAX(x,y) (x>y?x:y)

int main() {
	int a = 1;
	int b = 25;
	int E = MAX(a, b);
	printf("%d\n", E);
	#undef MAX
	int E = MAX(a, b); 	// err
	return 0;
}

(5)#和##

1.#运算符将宏的⼀个参数转换为字符串字⾯量。它仅允许出现在带参数的宏的替换列表中。 #运算符所执行的操作可以理解为”字符串化“。有⼀个变量 int x = 10; 的时候,打印出 the value of x is 10 . 就可以写:

#define PRINT(n) printf("the value of "#n " is %d", n);

 PRINT(x);当我们把x替换到宏的体内时,就出现了#x,而#x就是转换为

 printf("the value of ""x" " is %d", x);//预处理之后

2.## 可以把位于它两边的符号合成⼀个符号,它允许宏定义从分离的文本片段创建标识符。 ## 被称 为记号粘合 这样的连接必须产⽣⼀个合法的标识符。否则其结果就是未定义的。

#define GENERIC_MAX(type) \
type type##_max(type x, type y)\
{ \
 return (x>y?x:y); \
}
GENERIC_MAX(int) //替换到宏体内后int##_max ⽣成了新的符号 int_max做函数名 
GENERIC_MAX(float) //替换到宏体内后float##_max ⽣成了新的符号 float_max做函数名 
int main()
{
 //调⽤函数 
 int m = int_max(2, 3);
 printf("%d\n", m);
 float fm = float_max(3.5f, 4.5f);
 printf("%f\n", fm);
 return 0;
}

3.头文件的包含

#include 头文件包含属于预编译的过程,它其实也是进行了相关的文本替换。但C语言 的头文件分为两种,第一种是和库相关的库文件;第二种是本地文件包含。

本地头文件的查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头⽂件⼀样在 标准位置查找头⽂件。 如果找不到就提示编译错误。对于库⽂件也可以使用 “” 的形式包含,但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。

#include<stdio.h> //库文件
#include"SList.h" //本地文件

#include 指令可以使另外⼀个⽂件被编译。就像它实际出现于 #include指令的地方⼀样。这种替换的方式很简单:预处理器先删除这条指令,并用包含文件的内容替换。 ⼀个头⽂件被包含10次,那就实际被编译10次,如果重复包含,对编译的压力就比较大。

如果xxx.h⽂件比较大,这样预处理后代码量会剧增。使用条件编译指令来防止多次调用xxx.h。 

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

或者使用pragme

#pragma once
//文件

三、翻译环境

其实翻译环境是由编译和链接两个大的过程组成的,而编译又可以分解成:预处理(预编译)、编译、汇编三个过程

1. 预编译

预处理阶段主要处理那些源文件中#开始的预编译指令。如:#include,#define,处理的规则如下:
(1)将所有的#define 删除,并展开所有的宏定义。
(2)处理所有的条件编译指令,如: #if、#ifdef、#elif、#else、#endif 。
(3)处理#include预编译指令,将包含的头文件的内容插⼊到该预编译指令的位置,这个过程是递归进行的,也就是说被包含的头文件也可能包含其他文件。
(4)删除所有的注释
(5)添加行号和文件名标识,方便后续编译器生成调试信息等。或保留所有的#pragma的编译器指令,编译器后续会使用。经过预处理后的.i⽂件中不再包含宏定义,因为宏已经被展开。并且包含的头⽂件都被插⼊到.i⽂件中。所以当我们无法知道宏定义或者头⽂件是否包含正确的时候,可以查看预处理后的.i⽂件来确认。
 

2.编译

编译过程就是将预处理后的文件进行⼀系列的:词法分析、语法分析、语义分析及优化,生成相应的汇编代码文件。

词法分析:将源代码程序被输⼊扫描器,扫描器的任务就是简单的进行词法分析,把代码中的字符分割成⼀系列 的记号(关键字、标识符、字⾯量、特殊字符等)

语法分析:接下来语法分析器,将对扫描产生的记号进行语法分析,从而产生语法树。这些语法树是以表达式为节点的树。

语义分析:由语义分析器来完成语义分析,即对表达式的语法层⾯分析。编译器所能做的分析是语义的静态分 析。静态语义分析通常包括声明和类型的匹配,类型的转换等。这个阶段会报告错误的语法信息

3.汇编

汇编器是将汇编代码转转变成机器可执行的指令,每⼀个汇编语句⼏乎都对应⼀条机器指令。就是根 据汇编指令和机器指令的对照表⼀⼀的进行翻译,也不做指令优化

4.链接

链接是⼀个复杂的过程,链接的时候需要把⼀堆文件链接在⼀起才生成可执行程序。 链接过程主要包括:地址和空间分配,符号决议和重定位等这些步骤。 链接解决的是⼀个项目中多⽂件、多模块之间互相调用的问题。

  • 28
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值