当前位置:网站首页>C程序编译和预定义详解
C程序编译和预定义详解
2022-08-04 01:37:00 【浪雨123】
程序的编译和执行环境
c/c++不同于其他跑在虚拟机上的程序语言,c/c++在编写完代码,点击运行时,这段代码在编译器内先经过预编译,编译,汇编,链接等过程生成可执行程序文件,后缀为.exe,然后将可执行文件写入内存中,实行程序的运行。这篇文章我们简单探讨一下这几个过程究竟发生了什么事,最终的可执行程序是如何形成的。
预编译
在预编译过程,编辑器首先会删掉代码中的注释,因为注释是给程序使用者看的,代码文件不需要这些。删除注释之后,编辑器会将程序包含的头文件的声明的内容导入到该程序中,,然后将预定义的符号完成替换,如将 #define中的内容替换,这些工作完成之后,就会生成一个后缀为 .obj 的文件
编译
在编译这个阶段,会将前面预编译处理好的代码进行词法分析,语法分析,符号汇总,语义分析,分析完成后,再将分析完成的代码转换成汇编代码。汇编代码与机器码是一一对应的,无论哪种编程语言,都要在其编译器上将要执行的代码转换成汇编代码,然后再将汇编代码转换成机器码。不过在编译这个阶段,还没有将汇编代码转换成机器码,这是汇编阶段该做的事。
汇编
在汇编这个阶段,就是将前面编译阶段形成的汇编代码转换成机器码,并且将前面的符号汇总表示成一张符号表,到这里,对代码的处理工作就算是完成了,接下来就剩下链接这个阶段,下面一张图能更好的理解链接阶段。
链接
在一般开发项目中,会有多个源文件,每个源文件都难免会包含一些相同的头文件,而链接这个阶段就是将前面处理好的源文件进行一个汇总,将不同源文件中的符号表进行汇总,去除相同的只保留一个有效的,然后再去链接库抽取需要的代码,最终形成可执行文件。为什么说是去链接库抽取呢,如果是我们完全自己实现的头文件,就不需要像链接库抽取,但像引用了标准头文件,例如<stdio.h>里的printf,scanf等,这些函数的代码是放在链接库的,只有去链接抽取,才能够使用。当然这个过程是没有那么简单的,这里只是简单描述一下,想更深入的了解可以参考书籍《程序是怎样跑起来的》,《程序员的自我修养》
预定义
简单介绍一下程序的可执行文件是如何生成的,接下来我们回到预定义
预定义符号
以上是基本的预定义符号,不过编译器已经定义好了,我们无需重复定义,拿着用即可,上面的预定义符在写日志的时候会用到
预定义宏
在定义宏时的命名规定是宏名一般用大写字母表示,区别于其他符号
宏的本质是替换,因此在使用宏定义时,稍有不慎就会产生歧义,为了避免产生歧义,就要多使用括号,来分配优先级。
看下面一个例子
#define SQARE(x) x*x
int main()
{
int r = SQARE(5+1)
}
#define SQARE(x) ((x)*(x))
int main()
{
int r = SQARE(5+1)
}
//仔细看上面的代码,算一下它们各自的值是多少
为什么会这样呢?预定义的本质是替换,我们把数值替换过去试试
第一个 m = 5 + 1 * 5 + 1
第二个 n = ((5+1) * (5+1))
替换后再进行运算就发现了问题,第一个在进行替换后,因为运算优先级的问题,产生了歧义,导致程序未达到我们想要的结果,因此在预定义时不要吝啬括号。
一个练习题:写一个宏,用来计算结构体某成员相对于起始位置的偏移量
想一想再看下面的代码
#include<stdio.h>
#define STRUCTSIZE(STRUCTTYPE, MBERNAME) \
(int)&(((STRUCTTYPE *)0)->MBERNAME) //宏定义的实现
struct S
{
char a;
int b;
float c;
} s;
int main()
{
printf("%d", STRUCTSIZE(struct S, b));
return 0;
}
//思路:首先就是将数字0强制转化成要求的结构体的指针类型
那么这个0就是该结构体指针类型的0地址,结构体每个成员的偏移量,本质上就是相对于结构体起始位置的地址差,我们用这个起始地址为0的结构体指针解引用其内部成员,然后取出该成员的地址再强制转换成int 类型,就表示出了偏移量
#与##的用法
#的作用就是将要传的参数以字符串的形式表示
举个例子
//在这之前需要了解
printf("hello ""word\n");
printf("hello world\n");
//这两种写法是等价的,printf会将多组字符串合并成一串
#include<stdio.h>
#define PRINT(FORMAT, VALUE) \ //这里'\'的作用是抵消换行
printf("the value is "FORMAT, VALUE)
int main()
{
int a = 1;
int b = 2;
printf("the value is %d\n", a);
printf("the value is %d\n", b);
PRINT("%d\n", a);
PRINT("%d\n", b);
return 0;
}
//该代码段的程序起铺垫作用
#include<stdio.h>
int main()
{
int a = 1;
int b = 2;
printf("the value of a is %d\n", a);
printf("the value of b is %d\n", b);
return 0;
}
//看上面的程序,除了a,b的不同,其他都是相同的,因此我们能不能把
printf("the value of a is %d\n", a);
printf("the value of b is %d\n", b);
//封装成一个函数,只需要输入一个参数就能实现同样的输出
//但事实上,函数是无法实现的,因为函数没有办法表示出"the value of a is %d\n"中的 a,
"the value of b is %d\n"中的 b ,而用宏定义可以实现,#就派上了用场
#include<stdio.h>
#define PRINT(VALUE) \
printf("the value of "#VALUE" is %d\n", VALUE)
int main()
{
int a = 1;
int b = 2;
PRINT(a);
PRINT(b);
return 0;
}
//#会将要替换的参数以字符串的形式表示
//以上程序的替换结果如下
1.第一个将参数 VALUE 替换成a
printf("the value of "#VALUE" is %d\n", VALUE)
printf("the value of " "a" " is %d\n", a)
2.第二个将参数 VALUE 替换成b
printf("the value of "#VALUE" is %d\n", VALUE)
printf("the value of " "b" " is %d\n", b)
##的作用是将##两边的字符串连接在一起
举个栗子
#include<stdio.h>
#define FUN(VALUE1, value2) VALUE1##VALUE2
int main()
{
int langyu = 10;
printf("%d ", FUN(lang, yu));
return 0;
}
//FUN会将lang 和 yu结合在一起,形成langyu,程序最终的结果会打印10
函数与预定义的区别
#include<stdio.h>
#define SQARE(x) ((x)*(x))
int Sqare(int m)
{
return m*m ;
}
int main()
{
int r = SQARE(5+1);
int n = Sqare(5+1);
printf("%d %d", r, n);
return 0;
}
1.宏定义在处理时是直接替换,而函数的定义在处理时要在内存上开辟一处栈空间,所占用的内存远多于宏定义
2.函数栈帧的创建,理论上要执行更多的指令,在运行速度上宏定义也胜一筹
3.宏定义可以使用其他宏定义的符号,但是不能够递归调用,也就是不能够自己调用自己
4.对于实现较为复杂的功能,使用函数更为方便,但是有一些功能函数没有办法实现的,而预定义可以很好的实现,参考上面的#的使用
5.宏定义不会对参数进行检查,函数对参数检查较为严格,不容易出错
6.宏是没法调试的
条件编译
在编写代码时,可能有一段代码是用来测试结果的,如果不删,感觉有些碍事,但是删了的话下次调试又要重写,这个时候可以考虑使用条件编译,满足条件就参加编译,不满足条件就不参加编译,下面列出一些常见的条件编译
1.单支条件编译
#if 常量表达式
//...
#endif //如果常量表达式为真,则中间的代码参与编译,若为假,则不参加编译
2.多分支条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif //哪一个分支的常量表达式为真,那个分支下的代码就参与编译
3.判断是否被定义
#define X 10
#if define(X) //#ifdf X 这种写法可以平替
//...
#endif //如果X被定义了,那么中间的代码就参与编译,如果X未被定义,则不参与编译
#if !define(X) //#ifndf 这种写法可以平替
//...
#endif //如果X未被定义,则运行中间的代码,若被定义了,则不运行
4.嵌套指令
#if defined(X)
#ifdef OPTION1
//...
#endif
#ifdef OPTION2
//...
#endif
#elif defined(Y)
#ifdef OPTION2
//...
#endif
#endif
//条件预定义符可以嵌套使用
文件包含
在进行头文件包含时,我们通常有两种包含方式,一种是用 " " 来包含
两一种是用< > 来包含,那么这两种包含形式有什么区别呢?
这两种包含方式的主要区别是查找文件的策略不同
" " 这种包含方式编译器会首先查找该程序目录下的文件,如果查找不到就去库目录查
<>这种包含方式编译器会直接去库目录下查找
例如,标准库文件stdio.h就用<>来包含,编译器会直接去库文件查找
包含头文件后,编译器会在预编译阶段就去查找头文件,将包含信息替换成文件里的内容,此时若重复包含头文件,会造成包含内容的多次重复,无效代码量就增多
可能你会觉得自己怎么可能会重复包含某个头文件,重复包含的情况多出在多人开发上
那么该怎么避免重复包含的情况呢
有两种方法
1.较为繁杂
2.较为简便
方法一
#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif //__TEST_H__
//把要包含的头文件放到中间注释区域
方法二
#pragma once
//在文件的开始位置写上即可
边栏推荐
- boot issue
- Slipper - virtual point, shortest path
- flask框架初学-06-对数据库的增删改查
- 持续投入商品研发,叮咚买菜赢在了供应链投入上
- esp32发布机器人电池电压到ros2(micro-ros+CoCube)
- 多渠道打包
- 【日志框架】
- 实例039:有序列表插入元素
- Please refer to dump files (if any exist) [date].dump, [date]-jvmRun[N].dump and [date].dumpstream.
- Apache DolphinScheduler actual combat task scheduling platform - a new generation of distributed workflow
猜你喜欢
- heavy OpenCV 】 【 mapping
Jmeter cross-platform operation CSV files
How to find the cause of Fiori Launchpad routing errors by single-step debugging
nodejs+npm的安装与配置
网络带宽监控,带宽监控工具哪个好
jmeter distributed stress test
字符串的排列
即席查询——Presto
html select标签赋值数据库查询结果
Continuing to invest in product research and development, Dingdong Maicai wins in supply chain investment
随机推荐
实例040:逆序列表
VR panorama shooting online exhibition hall, 3D panorama brings you an immersive experience
appium软件自动化测试框架
循环绕过问题
什么是SVN(Subversion)?
flask框架初学-06-对数据库的增删改查
2022 China Computing Power Conference released the excellent results of "Innovation Pioneer"
Continuing to invest in product research and development, Dingdong Maicai wins in supply chain investment
nodejs安装及环境配置
MongoDB数据接入实践
GNSS[0]- Topic
C 学生管理系统_分析
【虚拟化生态平台】虚拟化平台搭建
nodejs切换版本使用(不需要卸载重装)
Apache DolphinScheduler新一代分布式工作流任务调度平台实战-中
nodejs+express realizes the access to the database mysql and displays the data on the page
114. How to find the cause of Fiori Launchpad routing error by single-step debugging
Google Earth Engine - Calculates the effective width of rivers using publicly available river data
Continuing to invest in product research and development, Dingdong Maicai wins in supply chain investment
【Untitled】