【C语言进阶(14)】程序的编译与链接

news2024/11/17 17:44:57

文章目录

  • 前言
  • Ⅰ 程序的翻译环境
    • 1. 编译的过程
    • 2. 链接的过程
  • Ⅱ 程序的执行环境
  • Ⅲ 预定义符号
  • Ⅳ 预处理指令 #define
    • 1. #define 定义标识符
    • 2. #define 定义宏
    • 3. #define 替换规则
  • Ⅴ 预处理操作符 # 和
    • 1. # 操作符
    • 2. ## 操作符
  • Ⅵ 宏和函数的对比
  • Ⅶ 预处理指令 #undef
  • Ⅷ 条件编译
    • 1. 单分支条件编译
    • 2. 多分支条件编译
    • 3. 判断符号是否被定义
  • Ⅸ 文件包含
    • 1. 本地文件包含
    • 2. 库文件包含

前言

在标准 C 语言的任何一种实现中,存在两个不同的环境:

  1. 翻译环境:将源代码翻译为可执行的机器指令。
  2. 执行环境:用于实际执行代码。

在这里插入图片描述

Ⅰ 程序的翻译环境

  • 当一个 .c 文件,最终要翻译成 .exe 文件时,需经过 翻译 + 运行两个环境。
  • 而翻译环境也需要经过两个过程:编译 + 链接

在这里插入图片描述

可执行程序的生成过程

在这里插入图片描述

  • 组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。
  • 每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。
  • 链接器同时也会引入标准 C 函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中。

不同环境下生成的目标文件

  • windows:生成的目标文件是 xxx.obj
  • Linux:生成的目标文件是 xxx.o

1. 编译的过程

编译分为 3 个过程:预编译 (预处理) → 编译 → 汇编

1. 预编译 (预处理)

在预处理完成后,会生成一个 xxx.i 文件。该文件会完成以下操作

  1. 注释的替换,将注释替换成空格。
  2. 头文件的包含,#include <>
  3. #define 符号的替换。

注:# 开头的都被称为预处理指令。所有的预处理指令都在预处理阶段就被处理掉。

在这里插入图片描述

2. 编译

C 语言代码翻译成汇编代码。执行过程如下

  1. 词法分析
  2. 语法分析
  3. 语义分析
  4. 符号汇总

3. 汇编

计算机无法看懂在编译阶段后翻译出的汇编代码,此时就需要使用汇编器将汇编代码翻译成二进制指令。生成了 .o 文件(目标文件)。

2. 链接的过程

  • 链接目标文件和链接库生成可执行程序 (二进制的程序)。
  1. 合并段表:将目标文件相同数据的段落进行合并。
  2. 符号表的合并和重定位

Ⅱ 程序的执行环境

程序运行的过程

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

Ⅲ 预定义符号

  • C 语言本身就预定义好的符号,这些符号可以被直接使用
符号名符号功能
__FILE__显示进行编译的源文件
__LINE__显示文件当前的行号
__DATE__显示文件被编译的日期
__TIME__显示文件被编译的时间
__STDC__如果编译器遵循 C 标准,其值为1,否则未定义
__FUNCTION__显示正在编译的是哪个函数

使用实例

  • 显示文件在编译过程中的文件、行号、日期、时间。

在这里插入图片描述

Ⅳ 预处理指令 #define

#define 的功能

  1. 定义标识符
  2. 定义宏

1. #define 定义标识符

语法形式

#define 名字 内容

举个例子

#define MAX 100 //MAX 就是标识符的名字,100 就标识符的内容

#define 的实现过程

  • #define 定义的标识符在预处理过程中,执行的是替换操作。不进行运算操作。

在这里插入图片描述

因为 #define 实现的是替换操作,所以在使用 #define 定义标识符时才不能出现分号。

#define MAX 100;	//如果出现分号

printf("%d\n",MAX);		//将 MAX 替换之后的结果就成了 printf("%d\n",100;);

2. #define 定义宏

#define 允许把参数替换到文本中,这种实现通常称为宏或定义宏。

宏的声明方式

#define 名字(参数列表) 内容
  • 其中的参数列表是一个由逗号隔开的符号表,它们可能出现在内容中。

举个例子

  • 利用宏来实现乘法

在这里插入图片描述

#define MUL(x,y) x * y
//1.将 2 + 3 和 4 + 5 传给 x y 成了 #define MUL(2 + 3,4 + 5)
//2.然后将替换后的 x y 传进内容中 #define MUL(2 + 3,4 + 5) 2 + 3 * 4 + 5 

int main()
{
	printf("%d\n", MUL(2 + 3, 4 + 5));
	//3.将 MUL(2 + 3,4 + 5) 替换成 2 + 3 * 4 + 5
	//结果就是 printf("%d\n",2 + 3 * 4 + 5);

	return 0;
}

定义宏时要舍得加括号

  • 因为宏实现的是替换值,所以宏在实现的过程中因为优先级的问题导致会很容易出现错误,因此在定义宏的时候,不要舍不得加括号。

在这里插入图片描述

3. #define 替换规则

在程序中扩展 #define 定义符号和宏时,需要涉及 3 个步骤:

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

注意事项

  1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
  2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

Ⅴ 预处理操作符 # 和

1. # 操作符

功能

  • 将一个宏的参数以字符串的形式插入到字符串中。

举个例子

在这里插入图片描述

2. ## 操作符

功能

  • 将位于它两边的符号合成一个符号,它允许宏定义从分离的文本段创建标识符。

举个例子

在这里插入图片描述

Ⅵ 宏和函数的对比

宏的优点

  1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
  2. 函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用 > 来比较的类型。宏是类型无关的

宏的缺点

  1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
  2. 宏无法调试。
  3. 宏由于类型无关,也就不够严谨。
  4. 宏可能会带来运算符优先级的问题,导致程序容易出现错。

宏和函数的对比

属性#define 定义宏函数优势方
代码长度每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码函数
执行速度更快存在函数䣌调用和返回的额外开销,相对慢一些
操作符优先级宏参数的求值实在所有周围表达时的上下文环境里,除非加上括号,非则邻近操作符的优先级可能会产生不可预料的后果,建议宏在书写时加多括号函数参数只在函数调用的时候求值一次,将它的结果值传给函数,表达式的求值结果更容易预测函数
带有副作用的参数参数可能被替换到宏体中的多个位置,所以带有副作用的参数值可能会产生不可预料的结果函数参数只在传参的时候求值一次,结果更容易控制函数
参数类型宏的参数有与类型无关,只要对参数的操作时合法的,它就可以用于任何参数类型函数的参数类型与类型有关,如果参数的类型不同,就需要不同的函数,即使它们执行的任务时不同的
调试宏不方便调试函数可以逐语句调试函数
递归宏不能递归函数可以递归函数

如果选择函数 / 宏

  • 如果逻辑比较简单,可以使用宏来实现。
  • 如果计算逻辑比较复杂,就要使用函数。

Ⅶ 预处理指令 #undef

  • 移除一个宏定义

举个例子

在这里插入图片描述

Ⅷ 条件编译

  • 在编译一个程序的时候将一条语句 (一组语句) 编译或放弃。

条件编译

  • 满足编译条件才允许执行编译。

以下为常见的条件编译指令

1. 单分支条件编译

语法格式

#if 常量表达式
	//代码
#endif
  • 表达式结果如果为真,中间的代码参与编译,反之不参与编译

举个例子

在这里插入图片描述

2. 多分支条件编译

语法格式

#if 常量表达式
	//...
#elif 常量表达式
	//...
#else
	//...
#endif

举个例子

在这里插入图片描述

3. 判断符号是否被定义

符号已定义则参与编译

#if defined(symbol)
#ifdef symbol

符号未定义则参与编译

#if !defined(symbol)
#ifndef symbol

举个例子

int main()
{
//以下为定义了符号才会去编译的条件编译指令
#if defined(M)	
	printf("world hello!\n");
#endif
#ifdef M
	printf("world hello!\n");
#endif

//以下为未定义符号才回去编译的条件编译指令
#if !defined(M)	//未定义 M 才编译代码
	printf("hello world!\n");
#endif
#ifndef M 
	printf("hello world!\n");
#endif 

	return 0;
}

在这里插入图片描述

Ⅸ 文件包含

1. 本地文件包含

包含的是自己的 .h 文件

语法格式

#include "xxx.h"

查找策略

  • 现在源文件所在目录下查找,如果该头文件未被找到,编译器则去标准位置查找头文件。
  • 如果找不到则提示编译错误。

举个例子

#include "test.h"
//寻找 test.h 文件
//1.先在当前的 .c 文件所在的文件夹下寻找
//2.如果没有找到,则取标注库文件夹下寻找
//3.如果在这两个地方都找不到则报错

在这里插入图片描述

2. 库文件包含

包含的是标准库的头文件

语法格式

#include <xxx.h>
  • 查找头文件直接去标准路径下去查找,如果找不到就提示编译错误.

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

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

相关文章

1.5状态压缩DP

1.小国王 在 n n nn nn的棋盘上放 k k k个国王&#xff0c;国王可攻击相邻的 8 8 8个格子&#xff0c;求使它们无法互相攻击的方案总数。 输入格式 共一行&#xff0c;包含两个整数 n n n和 k k k。 输出格式 共一行&#xff0c;表示方案总数&#xff0c;若不能够放置则输出…

RHEL 8.6 Kubespray 1.23.0 install kubernetes v1.27.5

文章目录 1. 预备条件2. download01 节点 安装 dockerdownload01 节点 介质下载下载 bastion01节点配置 yum 源bastion01 节点安装 docker5. 安装 docker insecure registrybastion01 部署 nginx 与 镜像入库13.1 配置 config.sh13.2 配置 setup-docker.sh13.3 配置 start-ngin…

18-spring 事务

文章目录 1. xml和注解配置方式的对象2.spring事务传播特性3. 注解事务的初始化流程4. 创建事务信息流程图5. 事务回滚流程图1. xml和注解配置方式的对象 2.spring事务传播特性 事务传播行为类型说明PROPAGATION_REQUIRED如果当前没有事务,就新建一个事务,如果已经存在一个事…

控制器连接Profinet转Modbus RTU网关与精密数显温控仪通讯配置案例

Profinet是一种用于工业自动化领域的通信协议&#xff0c;而Modbus RTU则是一种常见的串行通信协议。由于生产现场中的控制器与精密数显温控仪通常采用不同的通信协议&#xff0c;因此需要借助Profinet转Modbus RTU网关&#xff08;XD-MDPN100&#xff09;完成通信的桥接与转换…

SystemVerilog学习(1)——验证导论

写在最前 选课不慎&#xff0c;选修课选了个SystemVerilog&#xff0c;事情比必修还多&#xff0c;上课老师讲的一点用没有&#xff0c;但是学分还得修&#xff0c;只能自学了&#xff0c;既来之则安之。 一、什么是SystemVerilog SystemVerilog简称为SV语言&#xff0c;是一种…

攻防世界web篇-get_post

打开给出的地址 将浏览器上的地址http://61.147.171.105:58937/改为http://61.147.171.105:58937/?a1 输入?a1是完成了第一步&#xff0c;get请求&#xff0c;接下来要完成post的请求 这里&#xff0c;我使用的backbar quantum插件 得出flag值

【剑指Offer】32.从上往下打印二叉树

题目 不分行从上往下打印出二叉树的每个节点&#xff0c;同层节点从左至右打印。例如输入{8,6,10,#,#,2,1}&#xff0c;如以下图中的示例二叉树&#xff0c;则依次打印8,6,10,2,1(空节点不打印&#xff0c;跳过)&#xff0c;请你将打印的结果存放到一个数组里面&#xff0c;返…

深度强化学习 第 4 章 DQN 与 Q 学习

4.1 DQN 最优动作价值函数的用途 假如我们知道 Q ⋆ Q_⋆ Q⋆​&#xff0c;我们就能用它做控制。 我们希望知道 Q ⋆ Q_⋆ Q⋆​&#xff0c;因为它就像是先知一般&#xff0c;可以预见未来&#xff0c;在 t 时刻就预见 t 到 n时刻之间的累计奖励的期望。假如我们有 Q ⋆ Q…

【关于FPGA内部die到pin的延时数据,即pin delay获取方法】

首先&#xff0c;本文只介绍Xilinx的&#xff0c;Alteral的以后。。 第一&#xff0c;生成平台 Xilinx目前在用的是ISE,和Vivado&#xff1b;二者之间并不是可以互相替代的&#xff0c;或者说这两者不完全是迭代的关系。 第二&#xff0c;先介绍常用的–VIVADO 这里又有几种…

Linux系统之passwd命令的基本使用

Linux系统之passwd命令的基本使用 一、passwd命令介绍1.1 passwd命令简介1.2 passwd命令起源 二、passwd命令的使用帮助2.1 passwd命令的help帮助信息2.2 passwd命令的语法解释 三、查看passwd相关文件3.1 查看用户相关文件3.2 查看组相关文件 四、passwd命令的基本使用4.1 设置…

mysql MVC jsp实现表分页

mysql是轻量级数据库 在三层架构中实现简单的分页 在数据库sql编程中需要编写sql语句 SELECT * FROM sys.student limit 5,5; limit x,y x是开始节点&#xff0c;y是开始节点后的需要显示的长度。 在jdbc编程中需要给出x和y 一般是页数*页码&#xff0c;显示的长度。 代…

谷歌云:下一代开发者和企业解决方案的强力竞争者

自从2018年Oracle前研发总裁Thomas Kurian加入谷歌云&#xff08;Google Cloud&#xff09;并出任谷歌云CEO以来&#xff0c;业界对于谷歌云的发展就十分好奇。而谷歌云的前任CEO Diane Greene曾是VMware的创始人之一&#xff0c;那么两任企业级技术和解决方案出身的CEO&#x…

windows上下载github上的linux内核项目遇到的问题

问题一&#xff1a;clone的时候报错 Cloning into G:\github\linux... POST git-upload-pack (gzip 27925 to 14032 bytes) remote: Counting objects: 6012062, done. remote: Compressing objects: 100% (1031/1031), done. remote: Total 6012062 (delta 893), reused 342 (…

Android推送问题排查

针对MobPush智能推送服务在使用过程中可能出现的问题&#xff0c;本文为各位开发者们带来了针对MobPush安卓端推送问题的解决办法。 TCP在线推送排查 排查TCP在线收不到推送时&#xff0c;我们先通过客户端的RegistrationId接口获取设备的唯一标识 示例&#xff1a; MobPush…

【Reinforcement Learning】Ubuntu中mujoco210 mujoco_py D4RL安装及错误解决

Ubuntu中mujoco210 mujoco_py D4RL安装及错误解决 本文根据一篇知乎文章链接在此进行配置&#xff0c;记录在配置过程中遇到的一些问题&#xff0c;原文作者的教程很详细&#xff0c;在此对原作者表示感谢&#xff5e; 直接进行知乎原文的第2.2 有效安装过程(避坑) 2.注意上…

行业领先的三个企业正在利用聊天机器人变得更强

聊天机器人已成为客户服务领域的革命者&#xff0c;深刻地改变了企业与客户互动的方式。这些虚拟助手简化了交互&#xff0c;提供了24/7全天候高效和个性化的支持。凭借先进的技术和自然语言处理能力&#xff0c;聊天机器人擅长快速处理查询。 效率是聊天机器人的关键优势。它…

《数据结构、算法与应用C++语言描述》-队列的应用-图元识别问题

《数据结构、算法与应用C语言描述》-队列的应用-图元识别问题 图元识别 问题描述 数字化图像是一个 mxm 的像素矩阵。在单色图像中&#xff0c;每一个像素要么为0&#xff0c;要么为 1。值为0的像素表示图像的背景。值为1的像素表示图元上的一个点&#xff0c;称其为图元像素…

A_搜索(A Star)算法

A*搜索(A Star) 不同于盲目搜索&#xff0c;A算法是一种启发式算法(Heuristic Algorithm)。 上文提到&#xff0c;盲目搜索对于所有要搜索的状态结点都是一视同仁的&#xff0c;因此在每次搜索一个状态时&#xff0c;盲目搜索并不会考虑这个状态到底是有利于趋向目标的&#x…

STM32使用WWDG窗口看门狗

1 WWDG 介绍 1.1 WWDG 简介 窗口看门狗 WWDG 其实和独立看门狗类似&#xff0c;它是一个 7 位递减计数器不断的往下递减计数&#xff0c; 当减到一个固定值 0X40 时还不喂狗的话&#xff0c;产生一个 MCU 复位&#xff0c;这个值叫窗口的下限&#xff0c;是固定的值&#xf…

Android 开发技巧:音乐播放器的后台处理【Service、Handler、MediaPlayer】

给定部分完成的MusicPlayer项目&#xff0c;实现其中未完成的service部分&#xff1a; 1、创建MusicService类&#xff0c;通过service组件实现后台播放音乐的功能&#xff1b; 2、在MainActivity中通过ServiceConnection连接MusicService&#xff0c;实现对音乐播放的控制&…