C语言编译和链接

前言

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

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定义常量

基本语法:

cs 复制代码
#define name stuff

举例:

cs 复制代码
#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语句

cs 复制代码
int main()
{
   int n =0; 
   scanf("%d ",&n);
   switch(n)
{
   case :1;
   CASE :2;
   CASE :3;
   //.......
}

}

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

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

比如:

cs 复制代码
#define MAX 10000
#define MAX 10000;
cs 复制代码
if (condition)
   max  =MAX;
else
   max = 0;

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

2.1.1.2 #define定义宏

申明方式:

cpp 复制代码
#define name(parament-list) stuff

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

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

宏定义举例:

cs 复制代码
#define  SQUARE(X) X*X

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

警告:

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

cs 复制代码
int a = 5;
printf("%d ",SQUARE(a+1));

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

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

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

如何修改正确呢?

cs 复制代码
#define  SQUARE(x) ((x)*(x))

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

2.1.1.3 带有副作用的宏参数

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

例如:

cs 复制代码
x+1;//不带有副作用
x++;//带有副作用

证明:

cs 复制代码
#define MAX(a,b) ((a)>(b)?(a):(b))
x = 5;
y = 8;
Z = MAX(x++,y++);

预处理后:

cs 复制代码
#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. 宏会带来运算符优先级的问题,导致容易出错。

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

例如:

cs 复制代码
#define MALLOC(num,type) (type*) malloc (num * sizeof(type))

运用:

cs 复制代码
MALLOC(10,int);

预处理后:

cs 复制代码
(int *)malloc(10*sizeof(int));

宏和函数的对比

2.1.1.6 #和##
1. #运算符

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

cs 复制代码
printf("the value of n is %d",n);
cs 复制代码
#define PRINT(format,n) printf("the value of ""#n" "is"format,n);

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

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

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

2. ##运算符

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

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

cs 复制代码
#define GENERATE_MAX(type)\
type type##_max(type x,type y)\
{
  return (x>y?x:y);
}

运用:

cs 复制代码
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用于移除宏定义

格式:

cs 复制代码
#undef name
2.1.1.9 条件编译

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

常见条件编译指令:

cs 复制代码
#if
//。。。。
#endif
如:
#define __DEBUG__ 1
#if __DEBUG__
//......
#endif
  1. 多分支条件编译
cs 复制代码
#if 

#elif

#else

#endif
  1. 判断是否被定义
cs 复制代码
#if defined(symbol//写法1
#ifdef symbol//写法2

#if !defined(symbol)
#ifndef symbol
2.1.1.10 头文件包含
1. 本地文件包含
cs 复制代码
#include"filename"

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

2. 库文件包含
cs 复制代码
#include<filename.h>

到标准位置查找。

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

3. 嵌套文件包含

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

方法1:

cs 复制代码
#ifndef __TEST_H__
#define __TEST_H__
//....
//endif 

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

方法2:

cs 复制代码
#pragma once

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

2.2 编译

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

2.3 汇编

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

2.4 链接

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

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

相关推荐
奋斗的小花生1 小时前
c++ 多态性
开发语言·c++
魔道不误砍柴功1 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
闲晨1 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
老猿讲编程1 小时前
一个例子来说明Ada语言的实时性支持
开发语言·ada
Chrikk2 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*2 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue2 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man2 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
萧鼎4 小时前
Python并发编程库:Asyncio的异步编程实战
开发语言·数据库·python·异步
学地理的小胖砸4 小时前
【一些关于Python的信息和帮助】
开发语言·python