asp.net视频网站模板下载,郑州网站建设哪家,wordpress小说下载站,展会网站怎么做前言#xff1a; 由于c语言的程序编译链接的这块知识点不清楚#xff0c;回来复习一遍#xff0c;以便于好理解c知识#xff0c;我会尽快更新下一篇文章。 目录
1.程序的翻译环境和执行环境
2.翻译环境#xff08;编译链接#xff09;
编译#xff08;编译器#xf…前言 由于c语言的程序编译链接的这块知识点不清楚回来复习一遍以便于好理解c知识我会尽快更新下一篇文章。 目录
1.程序的翻译环境和执行环境
2.翻译环境编译链接
编译编译器
预编译(预处理)
1.头文件的包含
2.注释的测试
编译过程
汇编过程
链接
1.合并段表
2.符号表的合并和重定位
计算机语言的发展
运行环境(翻译之后)
3.预处理详解
3.1 预定义符号
3.2 #define
3.2.1 #define 定义标识符
3.2.2 #define 定义宏
3.2.3#define 替换规则
3.2.4 #和##
3.2.5 带副作用的宏参数 1.程序的翻译环境和执行环境
总体过程 在ANSI C的任何一种实现中存在两个不同的环境 第1种是翻译环境在这个环境中源代码被转换为可执行的机器指令。 第2种是执行环境它用于实际执行代码。 2.翻译环境编译链接
我们常说一个test.c文件要经过以下步骤才能产生可执行的程序那么具体是怎么做到的呢 在vs编译器上是要经过以下过程的: 源文件和目标文件的关系
链接库 VS是一个集成开发环境集成很多的功能ctr1F5不方便观察每个细节的功能接下来我使用gcc这个编译器给大家演示 由于上图的操作把这个编译运行的步骤一次走完了我们一步步来咱们用指令让程序停在预编译的阶段 编译编译器
预编译(预处理)
将控制台的内容放到test.i之后预处理阶段所做的事情
1.头文件的包含 2.注释的测试 编译过程 功能把C语言代码翻译成汇编代码 经历四个过程涉及一门课 -- 《编译原理》 1.语法分析 2.词法分析 3.语义分析 4.符号汇总 函数名全局变量 不会汇总 局部变量(函数运行起来才行) 推荐书籍:《程序员的自我修养》 链接https://pan.baidu.com/s/1cVcZRTj772hJXm0mO0Q_cw?pwd1234 提取码1234 编译阶段所做的事情,gcc操作过程: 汇编过程
1.把汇编代码转换成二进制的指令 2.形成符号表 链接 1.合并段表 2符号表的合并和重定位 1、2的作用其实是在链接期间为这种跨文件的代码进行协作的时候起作用的。 在vscode中操作 过程 1.合并段表 2.符号表的合并和重定位 把无效的地址给替换掉符号表和段表的关系是符号表是段表的内容 那该如何体现这个作用呢 假设我们在test.c文件底下extern了一个函数Add但是在add.c底下没有实现这个Add函数就会输出以下错误链接型错误 如果说这个函数写错的话,在main函数内调用大写的但是在add.c内定义成小写的是依然找不到 计算机语言的发展 运行环境(翻译之后) 程序执行的过程 1. 程序必须载入 内存 中。在有操作系统的环境中一般这个由操作系统完成。在独立的环境中程序的载入必须由手工安排也可能是通过可执行代码置入只读内存来完成。 2. 程序的执行便开始。接着便调用 main 函数(main函数第一行开始) 3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈 stack -- 函数栈帧 存储函数的局部变量和返回地址。 程序同时也可以使用静态static内存存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。 4. 终止程序。正常终止 main 函数也有可能是意外终止。 3.预处理详解
3.1 预定义符号 这些预定义符号都是语言内置的。 __FILE__ //进行编译的源文件 __LINE__ //文件当前的行号 __DATE__ //文件被编译的日期 __TIME__ //文件被编译的时间 执行 这个预定义符号也是语言内置的 __STDC__ //如果编译器遵循ANSI C其值为1否则未定义 而放在gcc编译器上面是可以执行通过的 编译器在代码编译的时候,会对函数和变量名重命名的,C 中会更加复杂 在C语言中重命名的规则基本就是加_ 3.2 #define
3.2.1 #define 定义标识符 语法 #define name stuff 全部例子 #define MAX 1000
#define reg register //为 register这个关键字创建一个简短的名字
#define do_forever for(;;) //用更形象的符号来替换一种实现
#define CASE break;case //在写case语句的时候自动把 break写上。
// 如果定义的 stuff过长可以分成几行写除了最后一行外每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf(file:%s\tline:%d\t \date:%s\ttime:%s\n ,\__FILE__,__LINE__ , \__DATE__,__TIME__ ) 分点叙述 #define MAX 1000 #define reg register//为 register这个关键字创建一个简短的名字 #define do_forever for(;;)//用更形象的符号来替换一种实现 #define CASE break;case //在写case语句的时候自动把 break写上。 长行如何拆分,\后面只能是换行不能是其他 如果定义的 stuff过长可以分成几行写除了最后一行外每行的后面都加一个反斜杠(续行符)。 提问 在define定义标识符的时候要不要在最后加上 ; ? 比如 #define MAX 1000; #define MAX 1000 建议不要加上 ; , 这样容易导致问题。 比如下面的场景 if ( condition ) max MAX ; else max 0 ; 这里会出现语法错误.
3.2.2 #define 定义宏 下面是宏的声明方式 #define name( parament-list ) stuff 其中的 parament - list 是一个由逗号隔开的符号表它们可能出现在 stuff 中 注意 参数列表的左括号必须与 name紧邻 。 如果两者之间有任何空白存在参数列表就会被解释为 stuff 的一部分 如 #define SQUARE( x ) x * x 这个宏接收一个参数 x . 如果在上述声明之后你把 SQUARE ( 5 ); 置于程序中预处理器就会用下面这个表达式替换上面的表达式 5 * 5 图解 警告 这个宏存在一个问题 观察下面的代码段 int a 5 ; printf ( %d\n , SQUARE ( a 1 ) ); 乍一看你可能觉得这段代码将打印 36 这个值。 事实上它将打印 11. 为什么图解 替换文本时参数x被替换成a 1,所以这条语句实际上变成了 printf (%d\n,a 1 * a 1 ); 这样就比较清晰了由替换产生的表达式并没有按照预想的次序进行求值。 在宏定义上加上两个括号这个问题便轻松的解决了 #define SQUARE(x) (x) * (x) 这样预处理之后就产生了预期的效果 printf ( %d\n ,( a 1 ) * ( a 1 ) ); 但是这样也会受到外部因素的影响 #define DOUBLE(x) (x) (x) 定义中我们使用了括号想避免之前的问题但是这个宏可能会出现新的错误。 int a 5 ; printf ( %d\n , 10 * DOUBLE ( a )); 这将打印什么值呢 warning 看上去好像打印 100 但事实上打印的是 55. 我们发现替换之后 printf (%d\n,10 * (5) (5)); 乘法运算先于宏定义的加法所以出现了55 . 这个问题的解决办法是在宏定义表达式两边加上一对括号就可以了。 #define DOUBLE( x) ( ( x ) ( x ) ) 提示 所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。 所以这就涉及到#define的替换规则了
3.2.3#define 替换规则 在程序中扩展 #define 定义符号和宏时需要涉及几个步骤。 1. 在调用宏时首先对参数进行检查看看是否包含任何由 #define 定义的符号。如果是它们首先被替换。 2. 替换文本随后被插入到程序中原来文本的位置。对于宏参数名被他们的值所替换。 3. 最后再次对结果文件进行扫描看看它是否包含任何由 #define 定义的符号。如果是就重复上述处理过程。 注意 1. 宏参数和 #define 定义中可以出现其他 #define 定义的符号。但是对于宏不能出现递归。 2. 当预处理器搜索 #define 定义的符号的时候字符串常量的内容并不被搜索。 3.2.4 #和## 如何把参数(变量名)插入到字符串中 首先我们看看这样的代码
char* p hello bit\n;
printf(hello bit\n);
printf(%s, p);这里输出的是不是hello bit 答案是确定的是 图解 我们发现字符串是有自动连接的特点的。 请看下图有没有那么一条语句将这三个变量传参传过去然后实现它们各自的输出呢 现在定义一个宏
#define print_format(num, format) \ printf(the value of #num is format)
并且让它输出 这时候发现需要正确输入它们的数值 这里只有当字符串作为宏参数的时候才可以把字符串放在字符串中。 1. 另外一个技巧是 使用 # 把一个宏参数变成对应的字符串 。 比如 代码中的 #value 会预处理器处理为 value ## 的作用 ## 可以把位于它两边的符号合成一个符号。 它允许宏定义从分离的文本片段创建标识符。 注这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。(也就是说这个拼接之后的变量名前提是要初始化一个值) 3.2.5 带副作用的宏参数 当宏参数在宏的定义中出现超过一次的时候如果参数带有副作用那么你在使用这个宏的时候就可能出现危险导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。 例如 x 1 ; //不带副作用 x ; //带有副作用 MAX 宏可以证明具有副作用的参数所引起的问题 在vscode2019内调试一下 想知道宏和函数的区别吗欲知后事如何请听下回分解。