C语言编译和链接

Ajiang2824735304 2024-08-16 17:35:02 阅读 98

前言

我们已经写了这么多的代码,那我们是不是应该了解一下代码是运行的呢?

1. 翻译环境和运行环境

翻译环境将源代码转换为二进制指令。

运行环境用于执行实际代码

2. 翻译环境

翻译环境主要由编译和链接两个大过程组成,而编译又可以分解成:预处理/预编译,编译,汇编三个过程。

2.1 预编译

预编译阶段,源文件和头文件被处理成为以.i为后缀的文件。

该阶段主要处理文件中以#开始的预编译指令,例如#include,#define,处理规则:

1. 将所有的#define删除,展开所以的宏定义。

2. 处理所有的条件编译指令,如#if、#endif、#ifdef 、#elif、#else。

3. 删除所有注释

4. 处理#include指令,将所包含的头文件的内容插入到预编指令位置,这个过程递归,即头文件可能包含其他文件。

5. 添加行号和文件号标识,方便后续编译器生成调试信息等。

6. 保留所有的#pragma的编译指令,编译器后续会使用。

2.1.1  宏定义

2.1.1.1 #define定义常量

基本语法:

<code>#define name stuff

举例:

#define M 100

#define A 50

#define CASE break;case

#define DEBUG_PRINT printf("file:%S\tline:%d\t\

date:%s\ttime:%s\n",\

__FILE__,__LINE__,\

__DATE__,__TIME__)

当定义的stuff过长,可以分成几行写,除了最后一行外,每行后面加一个反斜杠(续行符)。

通过第三行的代码可以以抽象的方式实现switch-case语句

int main()

{

int n =0;

scanf("%d ",&n);

switch(n)

{

case :1;

CASE :2;

CASE :3;

//.......

}

}

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

有些情况不能加;,有些情况可以加,建议是不加。例如下面的情况不能加

比如:

#define MAX 10000

#define MAX 10000;

if (condition)

max =MAX;

else

max = 0;

当替换之后,会出现两个分号,当没有大括号时,if后面只允许有一个语句,就会报错。

2.1.1.2  #define定义宏

申明方式:

#define name(parament-list) stuff

其中parament-list是由逗哈隔开的符号表,他们可能出现在stuff中。 

注意:左括号必须和name紧密相连,否则参数列表会被解释为stuff中的一部分。

宏定义举例:

#define SQUARE(X) X*X

当使用SQUARE(5) 时,预处理器将用 5*5替换SQUARE(5)。

警告:

上面的其实是有问题的,例如当遇到下面情况时:

int a = 5;

printf("%d ",SQUARE(a+1));

输出的结果不是36,而是11。

计算过程:5+1*5+1 = 11.

原因:宏定义代换时不会进行运算操作,只会进行简单代换。

如何修改正确呢?

#define SQUARE(x) ((x)*(x))

总结:当宏定义的时候尽量将每个参数都进行加括号操作,避免造成不可预估的结果。

2.1.1.3 带有副作用的宏参数

当宏参数在宏定义中出现超过一次的时候,如果参数带有副作用,那么使用的时候可能出现危险,导致不可预测的结果。

例如:

x+1;//不带有副作用

x++;//带有副作用

证明:

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

x = 5;

y = 8;

Z = MAX(x++,y++);

预处理后:

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

Z = ((x++)>(y++)?(x++):(y++));

进行运算后:x = 6, y = 10,z = 9。

2.1.1.4  宏替换规则

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

2. 替换文本随后被插⼊到程序中原来文本的位置。对于宏,参数名被他们的值所替换。

3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

注意:对于宏不能出现递归。

2.1.1.5 宏与函数的对比

宏通常被用于执行简单的运算。

如在找较大数时,写成宏更有优势。

原因:

1. 对于小型计算,宏不需要要调用函数栈帧等操作,节约时间。所以宏比函数在程序的规模和速度方面更胜一筹。

2. 宏的参数是类型无关的,什么类型都可以。

对比与宏的劣势:

1. 每次使用宏时,都需要插入代码,除非宏比较短,否则可能大幅度增加程序长度。

2. 宏没有办法调试。

3. 宏与类型无关,不够严谨。

4. 宏会带来运算符优先级的问题,导致容易出错。

宏的参数可以出现类型,但是函数不可以。

例如:

#define MALLOC(num,type) (type*) malloc (num * sizeof(type))

运用:

MALLOC(10,int);

预处理后:

(int *)malloc(10*sizeof(int));

宏和函数的对比

2.1.1.6 #和##
1. #运算符

当我们想写函数作为打印下列代码时,普通函数可能不是很好实现。因为这里面的n是在字符串里面的,带入参数时不好修改,而且如果打印时是其他参数呢?如%f等。那我们这里就可以运用宏来实现。

<code>printf("the value of n is %d",n);

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

这里的format是数据的类型,如果传的是%f,就会输出浮点数类型。%d就是整型类型。

这里的#n用来输出参数n的名称,而不是参数n的数据。

#执行的操作就是字符串化。

2. ##运算符

##运算符的作用是将两边的符号合成一个符号。##被称为记号粘合。

当求两个数较大数时,一个函数不能应用于多种类型,所以我们需要写不同的函数,那我们这里就可以用宏来实现。

#define GENERATE_MAX(type)\

type type##_max(type x,type y)\

{

return (x>y?x:y);

}

运用:

GENERATE_MAX(int)

GENERATE_MAX(float)

int main()

{

int m = int_max(2,3);

printf("%d\n",m);

return 0;

}

2.1.1.7 命名约定

我们通常将宏全部大写,函数名不全部大写。

2.1.1.8 #undef

#undef用于移除宏定义

格式:

#undef name

2.1.1.9 条件编译

当编译时,我们可以通过条件编译指令进行编译或放弃编译。

常见条件编译指令:

1.

#if

//。。。。

#endif

如:

#define __DEBUG__ 1

#if __DEBUG__

//......

#endif

2. 多分支条件编译

#if

#elif

#else

#endif

3. 判断是否被定义

#if defined(symbol//写法1

#ifdef symbol//写法2

#if !defined(symbol)

#ifndef symbol

2.1.1.10 头文件包含
1. 本地文件包含

#include"filename"

先在源文件目录查找,再到标准位置查找。

2. 库文件包含

#include<filename.h>

到标准位置查找。

总结:查找文件都可以用“”方式查找,但这样效率更低,且不容易区分是库文件还是本地文件。

3. 嵌套文件包含

当包含了多次同一文件时,为了避免造成资源的浪费,我们通过条件编译解决该问题。

方法1:

#ifndef __TEST_H__

#define __TEST_H__

//....

//endif

分析:先判断是否已经被定义,没被定义再定义,定义了则跳过。

方法2:

#pragma once

分析:这个表示只能被引入一次。

2.2 编译

将预处理后的文件进行一系列的:词法分析,语法分析,语义分析及优化,生成汇编代码。

2.3 汇编

将汇编代码转换为二进制代码。

2.4 链接

链接是一个复杂的过程,链接的时候需要将一堆文件链接在一起才生成可执行程序。

链接解决的是一个项目中多文件多模块之间相互调用的问题。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。