【C语言】编译链接 _Linux下操作 _#define详解 [进阶篇 _复习总结]

news2024/11/15 3:47:00

1.翻译环境和执行环境

1.1翻译环境

翻译环境又可以分为编译和链接,形成的可执行程序test.exe通过执行环境显示运行结果。

把源代码转换为可执行的机器指令(二进制指令),由编译器完成。

在这里插入图片描述

每个源文件经过编译器生成目标文件(windows下命名为xxx.obj,Linux下命名为xxx.o),目标文件生成后由链接器统一处理,并且会加上一些链接来的库,最后由链接器经过链接过程生成可执行程序。

在这里插入图片描述

库函数以来的库文件,都属于第三方库;如我们经常使用的printf、scanf函数都属于库函数。

1.2执行环境

程序执行的过程:

  1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
  2. 程序的执行便开始。接着便调用main函数。
  3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
  4. 终止程序。正常终止main函数;也有可能是意外终止。

实际执行代码,由操作系统完成。我经常使用的VS2019是一个集成开发环境,其中既有翻译环境,又有执行环境;

VS2019中的编译器是cl.exe,链接器是link.exe;这里我们可以看一下VS2019中的链接器(link.exe)是确实存在的:
在这里插入图片描述

2.编译本身的几个阶段

在这里插入图片描述

VS2019是集成开发环境,不方便观察每个细节;这里我们使用linux gcc来演示编译和链接的过程。

2.1 预处理(预编译)

在Linux环境下,创建两个文件test.c和add.c。

//test.c
#include <stdio.h>
extern int Add(int a, int b);
//定义一个宏NUM
#define NUM 100
int main()
{
    int n = NUM;
	int a = 10;
	int b = 20;
	int c = Add(a, b);
	printf("%d\n", c);
    
    #ifdef __DEBUG__
	printf("这是一个条件编译,debug下才会执行\n");
	#endif 
	return 0;
}
//add.c
int Add(int x, int y)
{
    return x + y;
}

想要知道预处理之中做了什么,就需要在预处理之后停下来,Linux下指令 gcc test.c -E -o test.i,gcc add.c -E -o add.i可以生成的test.i,add.i就是预处理之后的文件;下面通过观察test.i来看预处理阶段做了什么。

下面在Linux环境下进行操作:

在这里插入图片描述

下面打开test.i文件进行观察:

在这里插入图片描述

1.int n = NUM直接进行了替换,说明预处理阶段进行了宏替换

2.注释在预处理阶段之后就没了,说明预处理阶段去掉了注释

3.debug条件编译,因为gcc默认是release,所以看不到,说明预处理阶段处理了条件编译

还有一点就是我们#include <stdio.h>包含的头文件这里没有了,但是上面多了几百行的代码:

在这里插入图片描述

Linux环境下,头文件放在/usr/include中,那么我们对比一下库中的stdio.h:

在这里插入图片描述

对比可以得出预处理阶段的第四个处理功能:

4.头文件包含。

2.2 编译

想要知道编译阶段做了什么,同样需要在编译阶段后停下来;Linux下指令:gcc test.i -S -o test.s,gcc add.i -S -o add.s;生成的test.s,add.s就是编译阶段之后的文件。

下面来看一下test.s文件:

在这里插入图片描述

test.s中是一些汇编指令,需要进行语法分析、词法分析、语义分析、符号汇总; 《编译原理》-编译器的工作原理。

编译的主要作用就是把C语言代码转化为汇编代码。

编译的过程还是很复杂的,在这里给大家简单的介绍一下符号汇总(链接时需要用到):

汇总时只汇总全局的符号:main Add

在这里插入图片描述

2.3 汇编

在汇编阶段后停下来;Linux下指令:gcc test.s -c -o test.o,gcc add.s -c -o add.o;生成的test.o,add.o就是汇编阶段之后的文件。

下面观察下test.o文件:

在这里插入图片描述

可以发现,test.o中全是一些二进制指令,所以汇编阶段是把汇编指令转化为二进制指令,供计算机识别。

其中一个重要的阶段是形成符号表,每一个文件都形成自己的符号表。

Linux环境下:test.o 可执行程序的格式:elf,可以使用readelf工具进行读取。

readelf test.o -s (-s选项的作用就是显示符号表)

在这里插入图片描述

readelf add.o -s

在这里插入图片描述

形成的符号表(在链接时会使用到):

在这里插入图片描述

3.链接过程

1.合并段表

二进制文件,会被分成很多段;test.o,add.o都会被分为很多段,在合并段表的时候会把相同的段合并到一起。

2.符号表的合并和重定位

test.o中Add的地址是一个无效地址,add.o中Add的地址0x300是一个有效地址,合并的时候Add会合并为有效地址,而main函数是一个有效地址,不需要改变。

在这里插入图片描述

4.预处理详解

4.1#define定义标识符

#define NUM 100
#define STR "abcdef"

#define定义标识符还是很容易理解的,但是在写的时候需要注意:后面不要加分号,加分号可能会出现语法错误。

#defien NUM 100;
int main()
{
	int num = 0;
	if(1)
		num = NUM;
	else
		num = -1;
	return 0;
}

这样写在编译的时候就会报错:

在这里插入图片描述

if语句中没有{}时只能有一条语句,NUM带上分号之后if语句中是两条语句,出现了语法错误。

建议:在定义宏的时候后面不要加分号。

4.2#define定义宏

#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)。

//#define定义宏的语法:
#define name( parament-list ) stuff
#define MAX(x, y) (x>y?x:y)

注意:参数列表的左括号必须与name紧邻,如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分 。

下面看这样一段代码:

#define SQUARE(x) x*x
int main()
{
	int a = 9;
	int r = SQUARE(a+1);
	printf("%d\n", r);//19
	return 0;
}

这里有很多人就会有疑问了,为什么结果不是100,而是19呢?

这里我们通过得到预处理之后的代码来看一下:
在这里插入图片描述

可以看到的是在参数a+1传入宏中时,直接进行了替换。

在定义的时候可以改进一下:

#define SQUARE(x) (x)*(x)

下面再举一个例子:

#define DOUBLE(x) (x)+(x)
int main()
{
	int ret = 3*DOUBLE(20);//替换为3*(20)+(20)  80
	return 0;
}

总结一下:在使用#define定义宏的时候一定要检查一下括号,不使用括号得到的结果可能就和期望的结果不一样。

4.3#define的替换规则

在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
1.在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先
被替换。
2.替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
3.最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上
述处理过程。
注意:

1.宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。

2.当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/98860.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++初阶】string类各部分的使用介绍

文章目录1.为什么学string类&#xff1f;2. 标准库中的string类string类的介绍string类常用的接口说明a.常见的构造b.string类对象的常用容量操作c.string类对象的访问及遍历操作d.string类对象的修改操作1.为什么学string类&#xff1f; C语言中&#xff0c;字符串是以’\0’…

计算机毕设Python+Vue休闲网络宾馆管理(程序+LW+部署)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【图像分割】基于matlab Kmean聚类分水岭、oust、粒子群算法优化脂肪肝图像分割【含Matlab源码 2277期】

⛄一、粒子群算法自适应多阈值图像分割简介 FCM聚类算法是一种局部搜索算法,对初始值较为敏感,容易陷入局部极小值而不能得到全局最优解。PSO算法是一种基于群体的具有全局寻优能力的优化方法。本文将FCM聚类算法和PSO算法结合起来,将FCM聚类算法的聚类准则函数作为PSO算法中的…

MySQL简介与安装

文章目录MySQL简介与安装一、数据库相关概念二、MySQL数据库1. 版本2. 下载3. 安装1). 双击官方下载的安装包文件2). 根据安装提示进行安装3). 配置MySQL环境4. 启动停止5. 客户端连接6. 数据模型MySQL简介与安装 本文主要介绍数据库的相关概念、以及在Windows下安装MySQL 8.0…

matplotlib绘图详解

文章目录matplotlib绘图详解一、 基础准备1、 matplotlib简介2、 环境准备2.1 安装2.2 参考文档3、 图表结构4、 常用图表二、绘图步骤1、 常用图表1.1 散点图1.2 柱状图1.3 饼图1.4 折线图2、 多图绘制2.1 subplot2.2 subplots3、 绘图配置3.1常用配置3.1.1 网格线3.1.2 标题和…

【问答篇】Java 基础篇 面试题(二)

每天进步一点~ 01、问&#xff1a;PreparedStatement与Statement的区别&#xff1f; 答&#xff1a; PreparedStatement是预编译语句执行者&#xff0c;数据库对sql语句进行预编译&#xff1b;Statement是执行时对sql语句进行编译 Statement存在sql注入的问题&#xff0c;Prep…

非零基础自学Golang 第10章 错误处理 10.3 Go语言宕机 10.4 宕机恢复

非零基础自学Golang 文章目录非零基础自学Golang第10章 错误处理10.3 Go语言宕机10.4 宕机恢复10.4.1 recover捕获宕机10.4.2 recover应用第10章 错误处理 10.3 Go语言宕机 一般而言&#xff0c;只有当程序发生不可逆的错误时&#xff0c;才会使用panic方法来触发宕机。 pan…

云计算运营—02FusionSphere 6.5 服务器虚拟化运营

FusionSphere 6.5 服务器虚拟化运营 1、虚拟化 特性介绍 云操作系统架构 Tool/PV driver(虚拟机驱动) Tools是虚拟机的驱动程序。 安装并启动Tools后&#xff0c;用户无需做任何操作&#xff0c;Tools即可提供以下功能: 1、为虚拟机提供高性能的磁盘I/0和网络I/0功能 2、为…

Element-plus的通用使用方法

Element-plus的通用使用方法说明一、组件属性的使用二、组件内部事件的使用三、组件插槽的使用四、组件外部方法的使用1、使用外部方法是要调用标签的$refs2.使用方法完说明 本教程适用于Element-plus的使用非常熟练本教程是对Element-plus的高度总结组件属性的使用组件插槽的…

中国自然地理分区数据集 (含农业区划、森林工程、生态保护区、九大流域等)

一中国生态功能保护区数据 代码 名称 类型 1 秦岭山地生态功能保护区 水涵养生态功能保护区 2 若尔盖一玛曲生态功能保护区 水涵养生态功能保护区 3 滇西北生态功能保护区 水涵养生态功能保护区 4 珠江源(云南部分)生态功能保护区 水涵养生态功能保护区 5 雅鲁藏布江源头生态…

web前端-javascript-Array数组方法详解(增删改,遍历,截取,连接,转字符串,反转数组,排序)

文章目录数组的方法1. push() 添加2. pop() 删除3. unshift() 添加4. shift() 删除5. forEach() 遍历1) 说明2) 使用6. slice() 截取7. splice() 删除8. concat() 连接9. join() 转String字符串10. reverse() 反转11. sort() 排序1) 简单用法2) 自己指定排序规则数组的方法 1.…

[附源码]Python计算机毕业设计Django新能源汽车租赁

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

全文检索、精准查询、坐标查询、复合查询、排序、分页、高亮

文章目录DSL查询文档DSL查询分类全文检索查询精准查询地理坐标查询复合查询算分函数查询布尔查询搜索结果处理排序单字段 排序多字段 排序地理坐标排序分页高亮RestClient查询文档发起查询请求解析响应match查询精确查询布尔查询排序、分页距离排序高亮高亮请求构建高亮结果解析…

NAT处理流程、分类和使用场景

1、NAT分为源NAT、目的NAT、双向&#xff08;Bidirection&#xff09;NAT 源NAT适用于私网访问公网的情形 目的NAT适用于公网访问私网的情形 双向NAT适用于双方都非真实地址的情形 看下图&#xff0c;第一条匹配源NAT Server&#xff0c;如果匹配则2直接转换目的地址 ---> …

32位单片机存储器中地址膨胀映射作用位带操作和对应地址位置关系

映射的作用 所谓映射&#xff0c;实际上是有另一种控制同一个位置功能的方式。就相当于两个方式操作对一个功能起到相同作用 位带&#xff08;bitband&#xff09;操作:直译出来的&#xff0c;根本意思是将原地址中每一个字节位都扩展成占用一个32bit位的地址&#xff08;一个…

ubuntu18中ISCE2.6+stamps实现PSInSAR时序地表形表处理

一 安装ubuntu 教程参考&#xff1a;https://baijiahao.baidu.com/s?id1741674041146755999&wfrspider&forpc 建议安装18或者20版本&#xff0c;版本过高会导致版本不匹配的问题。 二安装Anaconda3 教程参考&#xff1a;https://zhuanlan.zhihu.com/p/269183148 注意…

[附源码]计算机毕业设计Python4S店汽车售后服务管理系统(程序+源码+LW文档)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

RBAC与连表查询

文章目录0. 前言1. RBAC模型1.1 什么是RBAC模型1.2 准备工作2. 连表操作2.1 常用连表图2.2 准备SQL语句2.3 Mybatis 连表3. 总结与补充0. 前言 连表学习、练习经典&#xff1a;RBAC权限模型、5表联查。 本文采用的持久层实现方式&#xff1a;Mybatis/Mybatis-Plus。 学习基础…

QT系列第6节 QT中常用控件

1. QComboBox 下拉列表 2. QPlainTextEdit QPlainTextEdit的文字内容以QTextDocument类型存储&#xff0c;函数document返回这个文档 对象的指针 QTextDocument是内存中的文本对象&#xff0c;以文本块方式存储&#xff0c;每个段落以换行符结束。 QTextDocument提供一些…

常用日期类

第一代日期类 Date类 Date&#xff1a;精确到毫秒&#xff0c;代表特定的瞬间 当需要获取当前时间时可以直接new一个Date对象&#xff0c;使用无参构造器 Date date new Date();日期转文本 但是直接使用的话&#xff0c;它是国外的时间格式&#xff1a;Sat Dec 17 17:02:3…