c语言中编译过程与预处理
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、c语言的编译与链接
- 1、编译与链接概述
- 2、编译与链接详解
- 二、c语言预处理
- 1.c语言中内置的预定义符号
- 2、#define定义标识符
- 3、#define定义宏
- 4、#define 替换规则
- 5、在使用#define定义宏时,#和##的作用
- 6、带副作用的宏参数
- 7、宏和函数对比
- 8、#undef
- 三、条件编译
- 1、常见的条件编译指令
- 四、文件包含
- 1、头文件被包含的方式
前言
一、c语言的编译与链接
1、编译与链接概述
在c语言的一个项目中会有很多.c文件,这些.c文件就是源文件,组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code),即每个源文件都转换为对应的目标文件,即.obj文件。
然后这些目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。
链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中。
编译器将这些存储代码的源文件编译成存储机器可以识别的二进制指令的目标文件,然后链接器将这些目标文件链接起来,因为test.c中使用了add.c和sub.c中定义的函数,所以链接这一步就是将test.c中使用的这些函数链接起来。然后才会形成一个可执行的程序。
2、编译与链接详解
源文件test.c和add.c在编译过程中,先进行预编译,在预编译阶段会将代码中的注释都去掉,然后像#define定义的标识符和宏等都会被替代掉,#include定义的头文件包含也会被引入其中,即test.i和add.i中有的是预处理过后的代码。可以看到test.c中的一些代码和注释在test.i中已经没有了。
在经过预处理后,下一步就是将生成的test.i文件进行编译了,该处理会将c语言代码翻译成汇编代码。并且会进行语法分析、词法分析、语义分析和符号汇总等。
最后编译器会将编译形成的test.cod汇编文件经过汇编处理变为机器可以识别的二进制指令。然后还会形成符号表。
然后就是链接器将生成的目标文件test.obj和add.obj进行符号表的合并与重定位,还有段表的合并。
test.cod在汇编后会将全局的一些函数等形成符号表,add.cod在汇编后也会将全局的一些函数等形成符号表,当在链接时,因为test.obj和add.obj的符号表中都给Add符号申请了地址,但是经过检查后发现test.obj里面Add的地址无用,所以会合并符号表,即当程序要调用Add函数时,就去0x 500地址去找该函数。
然后链接器将这些目标文件和该程序引用的库里面的一些文件都链接到一起,形成可执行程序test.exe。
二、c语言预处理
1.c语言中内置的预定义符号
这些预定义符号都是c语言内置的,可以直接使用这些符号来打印我们想要的信息。
使用这些预定义符号就可以得到当前语句执行的相关信息。
2、#define定义标识符
除了上面的预定义符号外,c语言也支持使用#define来定义标识符。
例如定义MAX 替代 100,则在下面的程序中如果使用了MAX,将都会在预处理阶段被替换为100.并且#define可以除了可以定义数字外,还可以定义字符、类型、代码等。
#define MAX 100
#define STR "abcdef"
#define FAC fac() //调用函数
//如果定义的语句过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)
#define DEBUG_PRINT printf("file:%s line:%d" \"date:%s time:%s\n",\__FILE__, __LINE__,\__DATE__,__TIME__)void fac()
{printf("hh\n");
}
int main()
{int a = MAX;printf("%d\n", a);printf("%d\n", MAX);FAC;DEBUG_PRINT;return 0;
}
可以看到我们在test.c中的注释和#define定义的标识符都在预处理阶段被去掉了,而且#define定义的标识符就是将这些标识符直接替换成了后面的语句。所以在使用#define定义标识符后,语句后边不用加上 ; 符,因为替换时会将 ; 符也加上,这样语句就会有两个 ; ; 语句结束符。
3、#define定义宏
#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(definemacro)。
#define name( parament-list ) stuff 其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。
注意: 参数列表的左括号必须与name紧邻。 如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。
在定义宏时应该尽可能多的使用括号,以避免出现下面的情况。
所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。
#define SQUARE(x) ((x)*(x))
#define DOUBLE(x) ((x)+(x))int main()
{int a = 9;int r = SQUARE(a); //81//经过预处理后,等价于 int r = ((a) * (a));printf("%d\n", r);int z = SQUARE(a + 1); //100//经过预处理后,等价与 int z = ((a + 1) * (a + 1));printf("%d\n", z);int ret = 3 * DOUBLE(100); //600//经过预处理后,等价于 int ret = 3 * ((100) + (100))printf("%d\n", ret);return 0;
}
4、#define 替换规则
在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
- 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
- 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
- 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意: - 宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。
- 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
#define ADD(x,y) ((x)+(y))
#define MAX 100int main()
{//会先将MAX替换为100,然后再将ADD(2,100)替换为 ((2)+(100))int ret = ADD(2, MAX);printf("%d\n", ret);//字符串里面的MAX不会被替换为100printf("MAX = %d\n", MAX);return 0;
}
5、在使用#define定义宏时,#和##的作用
使用 # ,把一个宏参数变成对应的字符串。
//宏可以做到这样的操作
//参数前面加上#就表示直接替换为N这个字符,而不是N的值
#define PRINT(N,format) printf(#N" = "format"\n",N)//函数不能将n改变为其他字符
void print(int n)
{printf("n = \n", n);
}
int main()
{int a = 10;double b = 3.14;//当我们想要输出这样的语句时,函数做不到,但是宏可以做到/*printf("a = %d\n", a);printf("b = %lf\n", b);*/PRINT(a,"%d");PRINT(b,"%lf");return 0;
}
##可以把位于它两边的符号合成一个符号。 它允许宏定义从分离的文本片段创建标识符。
#define CAT(name,num) name##numint main()
{int class101 = 101;printf("%d\n", CAT(class, 101));//在预处理时,会变为 printf("%d\n", class101)//##可以把位于它两边的符号合成一个符号。 它允许宏定义从分离的文本片段创建标识符。return 0;
}
6、带副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
例如
x+1;//不带副作用
x++;//带有副作用
即在宏执行的过程中,参数的值会发生变化。
#define MAX(x,y) ((x)>(y)?(x):(y))int main()
{int a = 5;int b = 8;int c = MAX(a++, b++);//在预处理后变为//int c = ((a++) > (b++) ? (a++) : (b++));printf("%d\n", a);printf("%d\n", b);printf("%d\n", c);return 0;
}
7、宏和函数对比
宏通常被应用于执行简单的运算。比如在两个数中找出较大的一个。
#define MAX(a, b) ((a)>(b)?(a):(b))
那为什么不用函数来完成这个任务? 原因有二:
1、用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
2、更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的。
当然和宏相比函数也有劣势的地方:
1、 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
2、 宏是没法调试的。
3、宏由于类型无关,也就不够严谨。
4、宏可能会带来运算符优先级的问题,导致程容易出现错。
宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。
#define MALLOC(num,type) (type*)malloc(num*sizeof(type))int main()
{int* p = (int*)malloc(10 * sizeof(int));int* p2 = MALLOC(10, int);//int* p2 = (int*)malloc(10 * sizeof(int));return 0;
}
命名约定: 把宏名全部大写 函数名不要全部大写。
8、#undef
这条指令用于移除一个宏定义。
#define MAX 100
int main()
{int a = MAX;printf("%d\n", a);printf("%d\n", MAX);
#undef MAX //此时MAX已经不再为定义的标识符printf("%d\n", MAX);return 0;}
三、条件编译
1、常见的条件编译指令
在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。
1、#if
#if 常量表达式//...
#endif
//常量表达式由预处理器求值。
如:
#define __ DEBUG __ 1
#if __ DEBUG __ //..
#endif
int main()
{int arr[10] = { 0 };int i = 0;for (i = 0; i < 10; i++){arr[i] = i + 1;
//如果#if 后面的常量表达式为真,则里面的语句会参与编译
#if 1printf("%d ", arr[i]);
#endif}return 0;
}
2.多个分支的条件编译
//...
#elif 常量表达式//...
#else //...
#endif
#define NUM 1
int main()
{//只有满足条件的语句才会编译,而不满足条件的语句在预处理阶段就被去除了
#if NUM==1printf("hehe\n");
#elif NUM==2printf("haha\n");
#elseprintf("heihei\n");
#endifreturn 0;
}
3.判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
#define MAX 0
int main()
{//如果使用#define定义了MAX标识符就为真
#if defined(MAX)printf("defined\n");
#endif#if !defined(MAX)printf("!defined\n");
#endif//下面两组和上面两组等价
#ifdef MAXprintf("ifdef\n");
#endif#ifndef MAXprintf("ifndef\n");
#endifreturn 0;}
4.嵌套指令
#if defined(OS_UNIX) #ifdef OPTION1 unix_version_option1(); #endif #ifdef OPTION2 unix_version_option2(); #endif
#elif defined(OS_MSDOS) #ifdef OPTION2 msdos_version_option2(); #endif
#endif
#define OPTION1 1#define FAC1 1
#define FAC2 2
#define FAC3 3int main()
{
#if defined(OPTION1)#ifdef FAC1printf("FAC1");#endif
#else #ifdef FAC2printf("FAC2");#endif
#endifreturn 0;
}
四、文件包含
我们已经知道, #include 指令可以使另外一个文件被编译。就像它实际出现于 #include 指令的地方一样。这种替换的方式很简单: 预处理器先删除这条指令,并用包含文件的内容替换。 这样一个源文件被包含10次,那就实际被编译10次。
1、头文件被包含的方式
本地文件包含
#include "filename"
查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。 如果找不到就提示编译错误。
库文件包含
#include <filename.h>
查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。
由上可知对于库文件,也可以用" "包含,但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。
嵌套文件包含
comm.h和comm.c是公共模块。 test1.h和test1.c使用了公共模块。 test2.h和test2.c使用了公共模块。 test.h和test.c使用了test1模块和test2模块。 这样最终程序中就会出现两份comm.h的内容。这样就造成了文件内容的重复。此时我们使用条件编译就可以解决这个问题
每个头文件的开头写:
#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif //__TEST_H__
或者
#pragma once
相关文章:
c语言中编译过程与预处理
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、c语言的编译与链接1、编译与链接概述2、编译与链接详解 二、c语言预处理1.c语言中内置的预定义符号2、#define定义标识符3、#define定义宏4、#define 替换规…...
TP-LINK 路由器设置内网穿透
TP-LINK 路由器设置内网穿透 开发中经常遇到调用第三方软件回调调试的情况,例如微信开发,支付回调等测试,用内网穿透是一种简单的方式也是偷懒的方式。 以TP-LINK路由器为例实现内网穿透 登录路由器 2.找到路由器虚拟服务器,添加…...
A 题国际旅游网络的大数据分析-详细解析与代码答案(2023 年全国高校数据统计与调查分析挑战赛
请你们进行数据统计与调查分析,使用附件中的数据,回答下列问题: ⚫ 问题 1: 请进行分类汇总统计,计算不同国家 1995 年至 2020 年累计旅游总人数,从哪个国家旅游出发的人数最多,哪个国家旅游到达的人数最多…...
《深入理解Java虚拟机》读书笔记: 类加载器
类加载器 虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。 类加载器可以说是Java语言的一项创新&…...
宝塔计划任务读取文件失败
想挂计划任务 相关文章【已解决】计划任务读取文件失败 - Linux面板 - 宝塔面板论坛 对方反馈的是执行下面的命令 chattr -ai /var/spool/cron 后来发现直接没有这个文件夹,然后通过mkdir命令创建文件夹,成功在宝塔创建了计划任务 后面发现任务虽然添…...
Python操作sql,备份数据库
1、批量执行sql import pymysql# 执行批量的 SQL 语句 def executeBatchSql(cursor, sqlStatements):for sql in sqlStatements:try:cursor.execute(sql)print(Executed SQL statement:, sql)except Exception as e:print(Error executing SQL statement:, e)# 创建数据库连接…...
Linux线程 --- 生产者消费者模型(C语言)
在学习完线程相关的概念之后,本节来认识一下Linux多线程相关的一个重要模型----“ 生产者消费者模型” 本文参考: Linux多线程生产者与消费者_红娃子的博客-CSDN博客 Linux多线程——生产者消费者模型_linux多线程生产者与消费者_两片空白的博客-CSDN博客…...
Vue2向Vue3过度核心技术computed计算属性
目录 1 computed计算属性1.1 概念1.2 语法1.3 注意1.4.案例1.5.代码准备 2 computed计算属性 VS methods方法2.1 computed计算属性2.2 methods计算属性2.3 计算属性的优势2.4 总结 3 计算属性的完整写法 1 computed计算属性 1.1 概念 基于现有的数据,计算出来的新属…...
芯片行业震荡期,数字后端还可以入吗?
自去年开始,芯片行业仿佛进入了动荡期,经历了去年秋招和今年春招的小伙伴都知道,如今找工作有多难。 半导体行业人才缩减、各大厂裁员,在加上高校毕业生人数破千万,对于即将踏入IC这个行业的应届生来说,今…...
“精准时空”赋能制造业智能化发展
作者:邓中亮 高达动态厘米级的高精度定位服务,不仅是北斗卫星导航系统的一大独门绝技,其在产业化应用层面也已逐步向普适化、标配化演进,并延展出时空智能新兴产业。 5月17日,当长征三号乙运载火箭成功发射北斗系统的…...
Kotlin协程flow发送时间间隔debounce
Kotlin协程flow发送时间间隔debounce debounce的作用是让连续发射的数据之间间隔起来。典型的应用场景是搜索引擎里面的关键词输入,当用户输入字符时候,有时候,并不希望用户每输入任何一个单字就触发一次后台真正的查询,而是希望…...
ServiceManager接收APP的跨进程Binder通信流程分析
现在一起来分析Server端接收(来自APP端)Binder数据的整个过程,还是以ServiceManager这个Server为例进行分析,这是一个至下而上的分析过程。 在分析之前先思考ServiceManager是什么?它其实是一个独立的进程,由init解析i…...
Git问题:解决“ssh:connect to host github.com port 22: Connection timed out”
操作系统 Windows11 使用Git IDEA 连接方式:SSH 今天上传代码出现如下报错:ssh:connect to host github.com port 22: Connection timed out 再多尝试几次,依然是这样。 解决 最终发现两个解决方案:(二选一…...
在Eclipse中创建javaweb工程
新建动态web工程 点击project或other之后,如何快速找到Dynamic Web Project 填写工程名等详细信息 也许会出现下面的对话框 项目结构图...
Pycharm链接远程mysql报错
Pycharm链接远程mysql配置及相应报错如下: 解决方法: 去服务器确认Mysql版本号: 我的Mysql为5.7.43,此时Pycharm mysql驱动为8.0版本,不匹配,所以需要根据实际的版本选择对应的驱动;选择对应的版…...
【硕士论文完美复现】【价格型需求响应】基于需求侧响应的配电网供电能力综合评估(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
Android Okhttp 源码浅析三
核心方法 getResponseWithInterceptorChain() internal fun getResponseWithInterceptorChain(): Response {// Build a full stack of interceptors.val interceptors mutableListOf<Interceptor>()interceptors client.interceptorsinterceptors RetryAndFollowUpI…...
一分钟学会用pygame制作棋盘背景
一分钟一个Pygame案例,这一集我们来学习一下如何生成一个视频中的棋盘背景效果,非常非常简单。 视频教程链接:https://www.bilibili.com/video/BV17G411d7Ah/ 当然我们这里是用来做页面的背景,你也可以拿来做别的效果࿰…...
Java --- 包装类
一、包装类 Java针对八种基本数据类型定义了相应的引用类型:包装类(封装类)。 二、基本数据类型与包装类的基本转换 public class WrapperTest {public static void main(String[] args) {//基本数据类型转换为包装类Boolean aBoolean new…...
[运维] wvp 28181安装部署全流程(ubuntu2204)
部署wvp 系统环境 系统版本:ubuntu2204 安装相关工具 sudo apt update ## 编译工具 sudo apt install git openjdk-11-jdk tar vim cmake gcc g libsrtp2-dev libssl-dev ## ffmepg sudo apt install ffmpeg编译zlm 编译zlm git clone https://gitee.com/xia-…...
vue实现富文本
效果图展示 一、安装依赖 npm install vue-quill-editor --save二、具体使用 html <template><!-- 富文本 --><quill-editorref"myQuillEditor"v-model"content":options"editorOption"blur"onEditorBlur($event)"…...
uniapp 开发微信小程序使用echart的dataZoom属性缩放功能不生效!bug记录!
在本项目中使用的是这个echart库 在项目中添加了dataZoom配置项但是不生效,突然想到微信小程序代码大小的限制,之前的echarts.js是定制的,有可能没有加dataZoom组件。故重新定制echarts.js。之前用的echarts版本是5.0.0,这次也是…...
用户端Web自动化测试_L4
目录: selenium多浏览器处理执行 javascript 脚本headless无头浏览器使用capability配置参数解析企业微信实战cypress测试框架介绍Playwright测试框架介绍 1.selenium多浏览器处理 多浏览器测试背景 用户使用的浏览器(firefox,chrome,IE 等)web 应用应该能在任何…...
CAPL - Panel和TestModule结合实现测试项可选
目录 一、定义脚本编号和脚本组编号 1、测试组定义 2、测试脚本编号定义...
机器学习,过拟合与欠拟合,正则化与交叉验证
目录 机器学习 过拟合与欠拟合 正则化与交叉验证 正则化 交叉验证 机器学习 的目的是使学到的模型不仅对已知数据而且对未知数据都能有很好的预测能力。 不同的机器学习方法会给出不同的模型。当损失函数给定时,基于损失函数的模型的训练误差(tra…...
gradio使用transformer模块demo介绍1:Text Natural Language Processing
文章目录 文本生成 Text Generation自动完成 Autocomplete情感分析 Sentiment Analysis命名实体识别 Name Entity Recognition NER多语种翻译文本生成 Text Generation import gradio as gr from transformers import pipelinegenerator = pipeline(text-generation, model=&l…...
算法通关村——数论经典问题解析
1. 辗转相除法 主要目的是获取两个数里面的最大公约数。 public int gcd(int a, int b) {int k 0;do {k a % b;a b;b k;} while (k ! 0);return a;}2. 素数和合数 素数的要求是必须大于等于2,并且只能被1和它本身整除。 判断的方法比较简单,就是从…...
代码随想录算法训练营第四十六天|LeetCode 1143,1035,53
目录 LeetCode 1143.最长公共子序列 动态规划五步曲: 1.确定dp[i][j]的含义 2.找出递推公式 3.初始化dp数组 4.确定遍历顺序 5.打印dp数组 LeetCode 1035.不相交的线 LeetCode 53.最大子序列和(动态规划) 动态规划五步曲: 1.确定…...
leetcode 541.反转字符串II
⭐️ 题目描述 🌟 leetcode链接:https://leetcode.cn/problems/reverse-string-ii/ ps: 这道题描述的有点晦涩难懂,意思就是每隔k个反转k个,末尾不够k个时全部反转,开始就不够k个也全部反转。 代码&#…...
MyBatis与Spring整合以及AOP和PageHelper分页插件整合
目录 前言 一、MyBatis与Spring整合的好处以及两者之间的关系 1.好处 2.关系 二、MyBatis和Spring集成 1.导入pom.xml 2.编写配置文件 3.利用mybatis逆向工程生成模型层代码 三、常用注解 四、AOP整合pageHelper分页插件 创建一个切面 测试 前言 MyBatis是一个开源的…...
深圳专门做网站的公司有哪些/seo教学免费课程霸屏
希望大家都能用选择和努力去惊艳时光 文章目录1、技术一面2、技术二面3、技术三面一、Linux必备知识最后:【可能给予你助力的教程】推荐阅读皮皮虾面试经历,希望对大家有帮助 1、技术一面 简单做一下自我介绍? 简要介绍一下项目/你负责的模块/选一个模…...
网站建设和网站开发/seo关键词外包公司
今天的煤矿跟以前最大的区别是什么?答案可以有很多,但有一个变化非常具有代表性:这是一个女孩儿们开始走进煤矿、走向生产一线的时代。以前的煤矿,当然是有女性身影的,她们或许是食堂里做饭的大姐,在某个场…...
专业网站建设怎么样/广州seo推荐
题目:原题链接(简单) 解法时间复杂度空间复杂度执行用时Ans 1 (Python)O(n)O(1)40ms (87.66%)Ans 2 (Python)––44ms (71.54%)Ans 2 (Python)––40ms (87.66%)LeetCode的Python执行用时随缘,只要时间复杂度没有明显差异&#x…...
城乡住房和城乡建设网站查询/免费合作推广
spring MVC框架controller间跳转,需重定向。有几种情况:不带参数跳转,带参数拼接url形式跳转,带参数不拼接参数跳转,页面也能显示。 首先先来介绍一下不带参数的重定向: 我在后台一个controller跳转…...
西安月子中心网站制作/seo数据是什么
asp.net教程:用ADO向Excel批量导入数据 和前面一篇用OleDB的方法类似,我们可以用ADO从RecordSet对象向Excel批量插入数据,这个方法无法自动复制字段名。 我们需要引用ADO和Excel的com对象 参考代码如下 usingSystem;usingSystem.Collections.Generic;us…...
新手学网站建设视频教程共30课高清版/网站搭建免费
计算机程序最基本的描述也许是一系列处理数据的操作,即使是最复杂的实例,这个论述也是正确的。应用程序的用户虽然看不到它们,但这些操作总是在后台上进行。我们在屏幕上进行的任何操作,无论是移动鼠标指针,单击图标&a…...