摘要:链接通过链接器将一个个目标文件或许还会有库文件链接在一起生成一个完整的可执行程序。在此过程中会发现被调用的函数未被定义。如果是,就重复上述处理过程。
老规矩笔记在gitee自取~:程序环境和预处理笔记
❤️欢迎喜欢学习C/C++的朋友互关一起努力!!❤️
在ANSI C(国际标准c语言)的任何一种实现中,存在两个不同的环境。
❄️翻译环境:在这个环境中源代码被转换为可执行的机器指令
❄️执行环境:它用于切实执行代码
下图详解(VS底下的编译器是cl.exe,连接器是link.exe
)
每个文件(.c文件)
都会经过编译器处理,变成目标文件(.obj)
头文件(.h)
的包含在预编译完成,生成文件(.i)
#include/#define/#pragma等,是预编译口令
注释也会在预编译阶段删除掉
编译阶段完成,将c语言代码转化成汇编代码,生成汇编代码文件(.s)
❄️语法分析
❄️词法分析
❄️语义分析
❄️符号汇总(只汇总全局符号
)
汇编代码进行汇编操作,将汇编代码转化成二进制代码,生成符号表并且生成目标文件(类型linux/vs .o/.obj)
此文件可以在debug目录(运行程序之后生成)下找到
文件中数据的存储格式为
elf格式
readelf指令可以查看
链接库
包含库函数文件
所有的目标文件+链接库
通过链接器编译成可执行文件(.exe)
链接各文件中操作:
⚡️合并段表
⚡️符号表的合并和符号表的重定位(相同的地方合并)
更深层次理解
☁️预处理:相当于根据预处理指令组装新的C/C++程序。
经过预处理,会产生一个
没有头文件(都已经被展开了)、宏定义(都已经替换了),没有条件编译指令(该屏蔽的都屏蔽掉了),没有特殊符号的输出文件
,这个文件的含义同原本的文件无异,只是内容上有所不同。
☁️编译:将预处理完的文件逐一进行一系列
词法分析、语法分析、语义分析及优化后
,产生相应的汇编代码文件。编译是针对单个文件编译的,只校验本文件的语法是否有问题
,不负责寻找实体。
☁️链接:通过链接器将一个个目标文件(或许还会有库文件)链接在一起生成一个完整的可执行程序。 链接程序的主要工作就是将
有关的目标文件彼此相连接
,也就是将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。
在此过程中会发现被调用的函数未被定义
。需要注意的是,链接阶段只会链接调用了的函数/全局变量,如果存在一个不存在实体的声明(函数声明、全局变量的外部声明),但没有被调用,依然是可以正常编译执行的。
以下符号在预处理阶段
处理
符号都是语言内置
,无需包含文件
__FILE__ //进行编译的源文件名__LINE__ //文件当前的行号__DATE__ //文件被编译的日期__TIME__ //文件被编译的时间__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
注意: __ STDC __ 在VS下不支持ANSI C所以未定义;而在Linux环境下,gcc对ANSI C支持
#define MAX 100int main(){ printf("%d", MAX); return 0;}
在预编译阶段
,MAX已经变成了100
注意:
⛄️标识符常量后不要加;
,容易出现问题
⛄️当预处理器/编译器中搜索不到
#define定义的符号
与函数不同,宏是把参数替换到文本中
#define ADD(x, y) ((x) + (y))int main(){ int a = 2; int b = 3; ADD(2, 3);//计算2+3 return 0;}
注意:
?参数列表的左括号
必须与宏名称紧邻
。
?如果两者之间有任何空白存在
,参数列表就会被解释为后面式子
的一部分。
?数值表达式进行求值的宏定义都应在外面加上括号
,避免在使用宏时由于参数中的操作符或邻近操作符产生作用
导致计算错误
?宏参数和#define定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归
?注意++和–等有副作用的符号
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。
#define MAX(a, b) ( (a) > (b) ? (a) : (b) )int main(){ int x = 5; int y = 8; int z = MAX(x++, y++); printf("x=%d y=%d z=%d/n", x, y, z); return 0;}
以这个三目操作符为例,参数a和b分别都出现了2次,三目操作符的意思是结果为真,返回a,结果为假,返回b
判断语句:a++ > b++ ?
这里
比较的时候
a和b是5和8
比较完
a和b是6和9返回结果:返回b的值,z被赋值为9
宏结束后:返回b++结束后,y为10
最后结果
#define INT int*typedef int* INT_T; int main(){ INT a, b;//这里是int* a; int b; INT_T c, d;//这里是 int* a; int* b }
?所以这里define仅仅是符号的替换
,而typedef起作用效果的对象,却是之后的所有变量
??如果这里的INT只是int* 效果也是一样
,所以typedef的优点
也在于此
???并且提醒大家以后定义变量尽量一行一个变量
以防出错
在定义符号和宏的时候,需要注意:
包含任何由#define定义的符号
,如果有将先被替换
原来文本的位置
值所替换
重复上述
处理过程。将一个宏参数变成对应的字符串
直接上一串代码体验:
我们想要实现printf的一个函数
int a = 1;int b = 2;printf("a的值是%d", a);printf("b的值是%d", b);//以上代码用函数实现void print(int x){ printf("x的值是%d", x);}
但是打印出来是:x的值是1,x的值是2
实现不了我们想要的:a的值是1,b的值是2
而define加上#
就有奇效:
#define PRINT(x) printf(""#x"的值为%d", x)int main(){ int a = 2; PRINT(a); return 0;}
结果是:
原理是什么样的?
???因为字符串有相邻自动连接功能
printf("hi""hello");
而#相当于将宏参数x左右加上""
使得x变成一个符号,这就是将宏参数变成对应的字符串
实际上,上面的""#x"的值为x" 实则是 " " 、 ""x" 、 "的值为%d ",连接起来
将两边的符号合成一个符号
#define PRINT(a, b) a##bint main(){ int a = 1; int b = 0; int ab = 10; printf("%d", PRINT(a, b));//相当于打印ab return 0;}
???注意:这样的连接必须产生一个
合法的标识符
,否则其结果就是未定义
不必过于深究,只要理解其作用就行
比较内容 | #define定义宏 | 函数 |
---|---|---|
代码长度 | 每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长 | 函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码 |
执行速度 | 更快 | 存在函数的调用和返回的额外开销,所以相对慢一些 |
操作符优先级 | 宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多些括号 | 函数参数只在函数调用的时候求值一次,它的结果值传递给函数。表达式的求值结果更容易预测 |
带有副作用的参数 | 参数可能被替换到宏体中的多个位置,所以带有副 作用的参数求值可能会产生不可预料的结果 | 函数参数只在传参的时候求值一 次,结果更容易控制 |
参数类型 | 宏的参数与类型无关,只要对参数的操作是合法 的,它就可以使用于任何参数类型 | 函数的参数是与类型有关的,如 果参数的类型不同,就需要不同的函数,即使他们执行的任务是 不同的 |
调试 | 宏是不方便调试的 | 函数是可以逐语句调试的 |
递归 | 宏是不能递归的 | 函数是可以递归的 |
命名约定:
#undef MAX//如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除
有一些编译器提供了一种能力,允许在命令行中定义符号
比如在linux环境下,gcc编译器,使用指令,在预处理阶段给
变量重新赋值
⛅️⛅️假定:某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大,我们需要一个数组能够大一点
满足条件,代码参与编译,不满足就不参与
int main(){ int a = 1;#if 0//这里写表达式或者符号(常量)//#if a 这里是错误的,因为在预编译过程a还没创建 //a创建的过程在运行的过程 printf("%d", a);//什么都不打印#endif return 0;}
对于宏
//定义了#if defined(symbol)#ifdef symbol//没有定义#if !defined(symbol)#ifndef symbol//两种写法均可
代码感受:
#define MAX 0int main(){ int a = 1;#if defined(MAX)//测试定义了没有,与值无关 printf("%d", a);#endif #if max//根据MAX的值判断 printf("%d", MAX);#endif return 0;}
运行结果:
#if 常量表达式 //...#elif 常量表达式 //...#else //...#endif
#if defined(MAX) #ifdef OP1 ADD1(); #endif #ifdef OP2 ADD2(); #endif#elif defined(MIN) #ifdef OP2 DEL2(); #endif#endif
用法有点类似if/else 语句
#include "test.c"
?先在源文件所在目录下
查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件
?如果找不到
就提示编译错误
#include
?查找头文件直接去标准路径
下去查找,如果找不到
就提示编译错误
可能存在一种,头文件在无意识中包含了两次,意味着预编译期间,会拷贝两份头文件内容
为了防止上述情况形成
我们用条件编译
解决这个问题
#ifndef TSD#define TSD#include //头文件的内容#endif
或者每个文件开头写
#pragma once
第二种方式最简单
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/122399.html
摘要:出于这些考虑,我选择了今天的主题每个程序员都可以懂一点。为什么要懂今天的主题是每个研发都可以懂一点,这是为什么其实很多技术是可以不懂的。 提到 Linux,作为程序员来说一定都不陌生。但如果说到「懂」Linux,可能就没有那么多人有把握了。到底用 Linux 离懂 Linux 有多远?如果决定学习 Linux,应该怎么开始?要学到什么程度?懂一点 Linux,对于程序员有什么价值?通过...
摘要:打个比方,就用曹冲称象这个例子吧。曹冲的方法把象放到大船上,在水面所达到的地方做上记号,再让船装载其它东西,称一下这些东西那么比较下就能知道了。就像曹冲的方法一样,是切实可行的,而像方法,则多少有点不切实际了。 ...
摘要:然而,在视觉的另一端,荔枝正在挖掘声音的一万种可能。今天,颖奇拜访了荔枝丁宁,一起来聊聊这个国内最大的声音互动平台,是怎样从创业团队发展成为拥有名研发人员的高效率平台。 · 本文内容为图文形式· 栏目:对话CTO· 阅读时间:15分钟· 阅读建议:深度长文,请配合文末福利慢慢食用· 掌握难度:★★★☆☆ 专栏介绍 「对话 CTO」是极客公园的一档最新专栏,以技术人的视角聊聊研发管理者的...
摘要:工欲善其事必先利其器游戏环境对比发表算法在游戏上超过人类之后,游戏研究迅速成为了研究热点。当然这不是网络游戏服务器架构概述一架构模型现代电子游戏,基本上都会使用一定的网络功能。 每个程序员都需要知道一些游戏网络知识 本文主要针对游戏的网络设计,在文章中目前主流的网络游戏实现方案都有讲解。从Peer-to-Peer 帧同步,客户端/服务器(c/s架构),客户端预测(Client-Side...
摘要:本文从入手,系统的回顾的异步机制及发展历程。需要提醒的是,文本没有讨论的异步机制。这就是之前提到的事件触发线程。其实无论是请求还是定时器还是事件,我们都可以统称它们为事件。第二阶段,引擎线程专注于处理事件。将外元素的事件回调放入调用栈。 functionshowImg(url){ varframeid=frameimg+Math.random(); window.img=window....
阅读 1822·2021-11-19 09:40
阅读 3068·2021-10-13 09:40
阅读 1797·2021-09-28 09:36
阅读 2066·2021-09-22 10:02
阅读 2586·2019-08-30 14:00
阅读 1820·2019-08-29 15:31
阅读 2777·2019-08-29 15:11
阅读 2781·2019-08-29 13:04