程序的编译 + 链接 + 预处理指令

文章目录

  • 一.程序的翻译和执行环境
  • 二.c语言程序的编译和链接
  • 三.预定义符号介绍
  • 四.预处理指令 #define
  • 五. # 和 ## 的作用
  • 六.宏和函数的对比
  • 七.条件编译
  • 八.文件包含

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

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

第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。 第2种是执行环境,它用于实际执行代码。

二.c语言程序的编译和链接

从总体结构上来讲,编译器会将每一个源文件进行单独编译,最终形成一个目标文件(后缀为.obj)
然后,链接器将多个目标文件和所需要的链接库进行链接形成可执行程序(后缀为.exe)

编译又可分为三个阶段

1.预编译阶段 :

预编译的目的是处理预编译指令

具体规则如下 :

(1).处理 “#include"预编译指令,将”#include"所包含的文件插入到该预编译指令的位置
(2).删除所有的注释
(3).将 #define所定义的符号进行替换
(4).处理所有的条件预编译指令,如 “#if” “#ifdef” “#elif” “#else” “#endif”
(5).保留所有的 #pragma编译器指令,因为编译器要使用它们.

经过预编译阶段,生成后缀名为 .i 的文件

2 .编译阶段 :

编译阶段的目的是将 c语言代码转换成汇编代码

程序在编译阶段所做的事情主要有以下几件 :

(1).词法分析

源代码会被输入到扫描器,扫描器扫描过后会产生一些记号,这些记号为 关键字 ,标识符 , 字面量以及一些特殊符号,与此同时,扫描器也会完成其他工作,如将标识符放到符号表中,字面量放到文字表中.

(2).语法分析

语法分析器对扫描器扫描所产生的记号进行语法分析,检查是否有语法错误

(3).语义分析

前面的语法分析器仅仅对语法进行了检查,但对语句是否有意义并未做检查,语义分析器对语句是否有意义进行检查

(4).符号汇总

对全局变量和函数进行汇总

经过编译阶段,生成后缀名为 .s 的文件

3.汇编阶段 :

汇编阶段的目的是将汇编代码转换成二进制指令,即最终得到的目标文件(后缀为.obj)

在汇编阶段将汇总的符号形成符号表

链接阶段

链接阶段的目的是将目标文件及链接库通过链接器形成可执行程序

程序在链接阶段所做的事情 :
(1).合并段表

目标文件是按照 elf 文件格式进行组织的,elf 文件结构由多个段组成 ,合并段表就是将目标文件的相同段进行合并

(2).符号表的合并和重定位

三.预定义符号介绍

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

用例 :

#include
int main()
{
	printf("%s\n", __FILE__);
	printf("%d\n", __LINE__);
	printf("%s\n", __DATE__);
	printf("%s\n", __TIME__);
}

四.预处理指令 #define

(1). #define定义标识符

#define name stuff

用例 :

#define MAX 100
#define DEBUG_PRINT printf("%s\t%d\t\
							%s\t%s\t\
							__FILE__,__LINE__\
							__DATE__,__TIME__");

(2). #define 定义宏

#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏或定义宏

#define name(parament-list) stuff 

其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。 注意: 参数列表的左括号必须与name紧邻。 如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部 分。

宏使用时的注意事项 :

(1).

#include
#define SQUARE( x )  x * x
int main()
{
	int a = 5;
 	printf("%d\n" ,SQUARE(a + 1) );
}

打印结果并不是我们所想要的 36 ,这是因为宏替换到文本中变成了 5 + 1 * 5 + 1 = 11

(2).

#include
#define DOUBLE(x) (x) + (x)
int main() 
{
	int a = 5;
	printf("%d\n" ,10 * DOUBLE(a)); 
}

打印结果并不是我们所想要的 100 ,宏替换到文本中变成了 10 * (5) + (5) = 55

因此我们在写宏的时候 , 记得要加上括号,否则可能会因为操作符优先级的问题导致没有得到我们想要的结果

(3).

#include
#define MAX(a, b)  ( (a) > (b) ? (a) : (b) ) 
int main()
{
	int x = 5,y = 8; 
	int z = MAX(x++, y++); 
	printf("x=%d y=%d z=%d\n", x, y, z);
	//输出的结果是什么? 
}

替换之后为 ( (x++) > (y++) ? (x++) : (y++) ) ,因为为后置++ , 5 > 8为假,执行 (y++) ,因此最终x = 6,y = 10,z = 9

五. # 和 ## 的作用

使用 # ,把一个宏参数变成对应的字符串

#include
#define print(data,format) printf("the value of " #data " is "format,data)
int main()
{
	int a = 10;
	print(a, "%d");
}
// 打印结果为 the value of a is 10

##可以把位于它两边的符号合成一个符号。 它允许宏定义从分离的文本片段创建标识符。

这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。

#include
#define ADD_TO_SUM(num,value) sum##num += value
int main()
{
	int sum5 = 10;
	ADD_TO_SUM(5, 10);
	printf("%d\n", sum5);
}
// 打印结果为 20

六.宏和函数的对比

属性 #define定义宏 函数
代码长度 每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长 函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码
执 行 速 度 更快 存在函数的调用和返回的额外开销, 所以相对慢一些
操 作 符 优 先 级 宏参数的求值是在所有周围表达式的上下文环境里,除非加 上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多些括号。 函数参数只在函数调用的时候求值一 次,它的结果值传递给函数。表达式 的求值结果更容易预测
带 有 副 作 用 的 参 数 参数可能被替换到宏体中的多个位置,所以带有副作用的参 数求值可能会产生不可预料的结果 函数参数只在传参的时候求值一次, 结果更容易控制。
参 数 类 型 宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用于任何参数类型。 函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数, 即使他们执行的任务是相同的
调 试 宏是不方便调试的 函数是可以逐语句调试的
递归 宏是不能递归的 函数是可以递归的

七.条件编译

在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。

(1).
#if 常量表达式
//…
#endif

#include
int main()
{
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", i);
#if 1
		printf("hehe\n");
#endif
	}
}

(2).
#if 常量表达式
//…
#elif 常量表达式
//…
#else
//…
#endif

#include
int main()
{
#if 2 + 3
	printf("5\n");
#elif 3 - 4
	printf("-1\n");
#elif 5 - 2
	printf("3\n");
#else 5 -5 
	printf("0\n");
#endif
}

(3). 判断是否被定义

#ifdef symbol
//…
#endif

#include
#define PRINT
int main()
{
#ifdef PRINT
	printf("hehe\n");
#endif
}

#ifndef symbol
//…
#endif

#include
int main()
{
#ifdef PRINT
	printf("hehe\n");
#endif
}

#if defined(symbol)
//…
#endif

#include
#define PRINT
int main()
{
#if defined(PRINT)
	printf("hehe\n");
#endif
}

#if !defined(symbol)
//…
#endif

#include
int main()
{
#if !defined(symbol)
	printf("hehe\n");
#endif
}

八.文件包含

头文件包含的两种方式 :

(1). 库文件包含

#include  

查找头文件直接去标准路径下查找,若找不到提示编译错误

(2). 本地文件包含

#include "filename.h" 

查找头文件首先去当前工程的目录下去查找,若查不到再去标准路径下去查找

防止头文件重复包含的两种方式 :

(1).

#ifndef __TEST_H__
#define __TEST_H__
// 头文件内容
#endif

(2).

#pragma once 

你可能感兴趣的:(c语言,c语言,编译,链接,预处理)