【C语言】程序的坎坷之路-从代码到可执行文件

众所周知,我们在编程开发工具进行代码的编写时,当代码编写完成后,我们只需要点击运行,代码就自动运行了,但是程序的运行真的像我们所看到的一样这么简单吗?

其实不是这样的,从程序点击运行开始,我们大致要经历四个步骤:
1.预处理
2.编译
3.汇编
4.链接
走完这四个步骤之后,我们的代码才会像我们看到的那样成功的运行起来了。那么为什么我们没有看到上述的四个步骤呢???其实是因为我们的编译器自动帮我们完成了这些工作,我们察觉不到而已。

那么这四个步骤又做了什么事呢?
举个例子:我们编写了这样一个代码

#include<stdio.h>
void main()
{
    int res = fun(1, 2);//fun函数计算两个参数的和并返回
    printf("%d ", res);
}

当我们运行这段代码的时候会报这样的错误
在这里插入图片描述
为什么程序会报找不到fun的错误呢?很明显,没有对fun函数进行定义,那么我们想知道,为什么编译器这么智能,它怎么知道我们的代码中存在未定义的函数呢?它是在哪个阶段发现了这个问题呢?

现在就让我们简单的了解一下代码是怎样一步步运行起来的

代码运行的坎坷之路

预处理

还是刚才的代码

#include<stdio.h>
#include"fun.h"
#define N 2;
void main()
{
    int res = fun(1, 2);//fun函数计算两个参数的和并返回
    printf("%d ", res);
}

预处理阶段读取当前的源文件的源程序,对#开头的指令进行处理,转换
一、把当前的头文件所包含的代码全部拷贝一份放到当前文件,注意这里的头文件可以分为两个类别,1)<>所包含的头文件,编译器会从安装路径下找这个头文件。2)""所包含的头文件,编译器会从当前工程目录下进行查找。
二、将宏定义进行替换。
三、生成.i文件

编译

编译阶段就会解决我们在开头提到的问题吗?其实也不是,这一阶段是在对代码进行词法分析,语法分析,语义分析和符号汇总。所以并不会检测到我们的函数没有定义。同时会生成.s文件。

汇编

在汇编阶段,就是我们经常说的汇编语言大放异彩的时候了,由汇编语言进一步转换为计算机能识别的二进制指令,生成可重定位目标文件.o文件
类似这样的汇编语言
在这里插入图片描述

链接

由多个.c文件生成的多个.o文件在这一步进行链接,最终生成一个可执行文件.exe文件。其实这一步才会检测到我们的代码中没有对fun函数进行定义,原因是因为fun函数没有定义就不会生成对应的.o文件,那么在链接的时候发现没有对应的fun.o文件进行链接,此时代码会报错。

四个阶段总结下来就是下面这幅图
在这里插入图片描述

我们简单介绍一下预处理所涉及到的一些知识点吧

预处理是在处理什么呢,上面的描述可能稍微有点简单,我们详细描述一下吧
预处理的内容包含
1.预定义符号
2.#define 标识符/宏
3.#undef 移除一个宏定义

1.预定义符号

__FILE__      //进行编译的源文件
__LINE__     //文件当前的行号
__DATE__    //文件被编译的日期
__TIME__    //文件被编译的时间
__STDC__    //如果编译器遵循ANSI C,其值为1,否则未定义
//这些在哪里会被用到呢,例如:
printf("file:%s line:%d\n", __FILE__, __LINE__);

2.#define 标识符/宏

//标识符
#define N name//意味着我们将name这个名字定义为N,之后代码中遇到N就用name替换即可
#define MAX 500//我们将MAX定义为500这个数值
#define CASE break;case //在写case语句的时候自动把 break写上

//宏	
#define SQUARE( x ) x * x//这时我们定义了一个宏函数,但是宏函数在计算时容易出错,那是因为我们容易在调用SQUARE(5+1)时将这个值先加起来,实际上,我们应该原封不动的将5+1这个表达式替换为 5+1*5+1

当进行定义宏的时候,给人的感觉和定义一个函数非常相似,实际上宏和函数确实有许多方面非常相似,不过二者皆有利有弊。
宏的优势

1.用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序 的规模和速度方面更胜一筹。
2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可 以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的。

宏的劣势

1.每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
2. 宏是没法调试的。
3.宏由于类型无关,也就不够严谨。
4.宏可能会带来运算符优先级的问题,导致程序容易出现错。

最后介绍一个容易出现的问题:头文件的重复引入
在这里插入图片描述

common.h公共头文件,分别被test1.c和test2.h引入,同时又都被main.c包含了,程序会报重复引入的错误,那么如何避免这个问题呢?条件编译一下
我们在每个头文件下都进行这样的条件编译,问题就解决了!

#ifndef __TEST_H__ 
#define __TEST_H__ 
//头文件的内容
#endif //__TEST_H__ 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值