C语言——编译和链接
(图片由AI生成)
0.前言
C语言是最受欢迎的编程语言之一,以其接近硬件的能力和高效性而闻名。理解C语言的编译和链接过程对于深入了解其运行原理至关重要。本文将详细介绍C语言的翻译环境和运行环境,重点关注编译和链接的各个阶段。
1.翻译环境和运行环境(简介)
在C语言编程中,翻译环境和运行环境是两个关键的概念,它们共同定义了程序从编写到执行的整个过程。
翻译环境
翻译环境涉及将C语言源代码转换为机器可执行代码的过程。这一过程分为几个阶段:首先是预处理,处理源代码中的预编译指令,例如宏定义和文件包含。紧接着是编译阶段,编译器将处理过的代码转换为汇编语言。然后,汇编器将汇编代码转换为机器代码,生成目标文件。最后,链接器将多个目标文件和库文件合并,生成最终的可执行文件。这一过程的核心目的是将高级语言编写的程序转换为计算机能够直接理解和执行的低级语言程序。
运行环境
运行环境则是指程序执行时所依赖的环境,包括硬件和操作系统。在运行环境中,操作系统负责为程序提供所需的资源,如内存管理、输入/输出处理等。运行环境确保编译后的程序能够在特定的硬件和操作系统上顺利运行,执行其设计的功能。运行环境的稳定性和兼容性直接影响程序的性能和效率。
2.翻译环境
翻译环境是C语言编程中将源代码转换成机器可执行代码的整个过程。这个环境涉及几个关键的步骤,从预处理开始,一直到编译、汇编,最后是链接。
一个C语言项目中可能包含多个.c文件,而多个.c文件生成可执行程序的方法是什么呢?
- 在编译阶段,项目中的多个
.c
文件会被单独编译,生成对应的目标文件。 - 不同的操作系统环境下,目标文件的格式略有不同。例如,在Windows环境下,目标文件的后缀通常是
.obj
,而在Linux环境下,则是.o
。这些目标文件包含了源代码编译后的机器代码,但尚未进行最终的链接。 - 编译后的目标文件接着会被送入链接阶段。在链接阶段,多个目标文件和链接库一起经过链接器的处理,最终生成可执行程序。
- 链接库可以是运行时库,即支持程序运行的基本函数集合,也可以是第三方库,提供额外的功能和服务。链接器的任务是将这些分散的代码和资源整合,解决程序中的外部引用问题,确保程序能够在运行环境中顺利执行。
如果把“编译”展开为3个过程(预处理、编译和汇编),则流程图如下:(以GCC为例)
2.1预处理(预编译)
预处理是C语言编译过程中的第一阶段,发生在实际编译之前。这一阶段主要由预处理器处理源代码中的预处理指令。预处理器是编译器的一部分,它对源代码进行初步的处理,为编译阶段做准备。在这个阶段,预处理器执行以下任务:
-
宏定义的展开:预处理器会查找源代码中所有以
#define
指令定义的宏,并将它们替换成相应的值或代码片段。这一步是在编译器实际分析代码之前完成的,它可以用于条件编译或简化代码书写。 -
文件包含处理:对于源代码中的
#include
指令,预处理器会将指定的文件内容插入到该指令所在的位置。这通常用于包含标准库头文件或其他源文件,使得函数声明和宏定义在多个文件中可以共享。 -
条件编译:预处理器支持条件编译指令,如
#if
、#ifdef
、#ifndef
、#else
和#endif
。这些指令允许根据特定的条件(通常是宏定义是否存在)来决定是否编译某部分代码。 -
移除注释:预处理器会删除源代码中的注释,因为注释对程序的执行没有影响,只服务于程序员阅读和理解代码。
那么我们该如何直观地观察到预处理前后文件的变化呢?在GCC环境下的命令如下:
gcc -E test.c -o test.i
通过VScode中GCC编译器的操作实例,我们不难发现在预处理(test.c变成test.i)的过程中,头文件<stdio.h>在.i文件中展开(前881行),所有的MAX都被替换成了100,并且
#include<stdio.h>
#define MAX 100
以及两个注释均被删去。关于条件编译的部分,我们将在后续博客中作介绍,敬请期待。
2.2编译
编译是C语言翻译环境中的关键阶段,其主要任务是将预处理后的源代码转换为汇编语言。编译过程可以分为三个子阶段:词法分析、语法分析和语义分析。
编译过程的命令如下:
gcc -S test.i -o test.s
操作界面如下图所示:
我们将结合代码 int a = x > y ? x : y;
来展示词法分析、语法分析和语义分析的过程。
2.2.1词法分析
词法分析是编译的第一步。在这个阶段,编译器的词法分析器(也称为扫描器)对源代码进行扫描,将代码字符串分解为一系列的词法单元(tokens)。这些词法单元包括关键字(如if
、while
)、标识符(如变量和函数名)、常量、字符串字面量和符号(如+
、-
、*
、/
)等。
词法分析的主要任务是识别出源代码中的各种基本元素,并去除空白字符、换行符等无关内容,为后续的语法分析阶段提供清晰、简化的输入。
在词法分析阶段,编译器将这行代码分解为一系列词法单元(tokens)。这个过程大致如下:
int
- 关键字,表示整数类型。a
- 标识符,代表变量名。=
- 运算符,表示赋值。x
- 标识符,代表变量名。>
- 运算符,表示大于比较。y
- 标识符,代表变量名。?
- 运算符,表示条件表达式的开始。x
- 标识符,代表变量名。:
- 运算符,用于条件表达式,区分不同的输出。y
- 标识符,代表变量名。;
- 分号,表示语句结束。
2.2.2语法分析
接下来的语法分析阶段,编译器使用词法分析得到的词法单元来构建抽象语法树(Abstract Syntax Tree,AST)。在这个过程中,编译器检查代码是否遵循C语言的语法规则。语法分析器需要识别各种语法结构,如表达式、语句、函数定义等,并确保它们正确地组合在一起。
如果代码中存在语法错误,如缺少分号、括号不匹配等,语法分析器会在这个阶段发现并报告这些错误。语法分析是确保程序结构正确的重要步骤。
在语法分析阶段,编译器使用上述词法单元来构建抽象语法树(AST)。这个代码段大致对应于以下结构:
- 声明语句
- 类型:
int
- 变量:
a
- 赋值表达式
- 左边: 变量
a
- 右边: 条件表达式
- 条件部分: 比较表达式 (
x > y
) - 真值部分: 变量
x
- 假值部分: 变量
y
- 条件部分: 比较表达式 (
- 左边: 变量
- 类型:
2.2.3语义分析
最后,编译过程进入语义分析阶段。在这个阶段,编译器检查源代码的语义正确性,确保程序中的每个操作都是有意义的。语义分析包括变量和函数的声明检查、类型检查、表达式中运算符的有效性检查等。
例如,编译器会检查变量是否在使用前已被声明,函数调用是否与函数定义匹配,以及表达式中是否存在类型不兼容的情况。语义分析是保证程序行为符合预期的关键步骤。
在语义分析阶段,编译器检查代码的语义正确性。针对这段代码,编译器将执行以下操作:
- 确认
x
和y
已被声明并定义(如果之前没有声明,这将是一个语义错误)。 - 确认
x
和y
的类型可以进行>
比较操作。 - 确认条件表达式的两个输出(
x
和y
)类型相同,或者至少是可以被隐式转换成同一类型,以便赋值给a
。 - 确认整个表达式的结果可以被赋值给左侧的变量
a
,即a
的类型(在这个例子中是int
)应该能够容纳条件表达式的结果。
通过这样的分析,编译器确保了代码不仅在结构上正确,而且在逻辑和操作上也是合理的。如果任何一步检查失败,编译器将报告一个语义错误,如类型不匹配或未声明的变量等。
2.3汇编
汇编阶段是C语言编译过程中的一个关键步骤,它紧随编译阶段之后。在这个阶段,编译器生成的汇编代码被转换为机器代码,这是计算机能够直接理解和执行的代码形式。
2.3.1原理
汇编器的主要任务是将汇编语言(一种低级语言,比机器代码更易于人类理解)转换为机器代码。汇编语言由一系列指令组成,这些指令对应于CPU的操作。每个汇编指令通常对应于一条机器指令。
在汇编阶段,汇编器接收由编译器生成的汇编代码,并将其转换为目标机器的机器代码。这个过程包括解析汇编指令和符号(如变量和函数名),并将它们转换为机器指令和地址。
2.3.2GCC命令
在使用GCC(GNU Compiler Collection)这个在Linux和其他类Unix系统中常用的编译器时,汇编阶段通常是自动进行的。不过,你也可以手动控制这个过程。例如,要将C代码编译为汇编代码,可以使用以下GCC命令:
gcc -S [filename].c
这个命令会生成一个.s
文件,这是一个汇编语言文件,它包含了由C源代码转换而来的汇编指令。
为了进一步将汇编代码转换为机器代码(生成目标文件),可以使用:
gcc -c [filename].s
这个命令会生成.o
(在Linux系统上)或.obj
(在Windows系统上)后缀的目标文件,这是包含机器代码的文件,它可以被链接器进一步处理以生成最终的可执行文件。
我们不妨试一试:(注意:test.o是二进制文件,是给计算机看的,人一般看不懂)
我们如果强行用记事本打开test.o文件,则会出现一些乱码:
2.4链接
链接是C语言编译过程的最后一个阶段。在这个阶段,链接器(Linker)负责将编译和汇编过程生成的一个或多个目标文件(.o
或.obj
文件),以及所需的库文件,合并成最终的可执行程序。
2.4.1链接的主要任务
-
解析符号:链接器首先解析出程序中的所有符号,如函数和变量名。它需要处理的主要问题是,找出每个符号的定义,并将其与引用该符号的地方连接起来。
-
地址和空间分配:链接器分配内存地址给各个程序段和变量。它会根据每个目标文件的相对地址信息,计算出实际运行时的绝对地址。
-
解决外部依赖:链接器会处理目标文件和库文件之间的依赖关系,例如,如果你的程序调用了标准库函数,链接器会从标准库中找到这些函数的实现,并将其与你的代码相连接。
-
生成可执行文件:最终,链接器生成一个可执行文件,这个文件包含了所有必要的代码和数据,以便在目标平台上运行。
2.4.2实例
假设你有两个C文件:main.c
和functions.c
。
main.c
包含主函数和对functions.c
中定义的函数的调用。functions.c
包含一些定义的函数。
2.4.3步骤
- 编译:首先,使用编译器(如gcc)分别编译这两个文件,生成两个目标文件。
- 链接:然后,将这些目标文件链接成一个可执行文件。
//1.编译
gcc -c main.c -o main.o
gcc -c functions.c -o functions.o
这将分别为 main.c
和 functions.c
生成 main.o
和 functions.o
目标文件。
//2.链接
gcc main.o functions.o -o program
- 这个命令会将
main.o
和functions.o
链接在一起,生成可执行文件program
。
在这个过程中,链接器会执行上述的任务。例如,如果 main.c
中调用了 functions.c
中定义的函数,链接器会确保这些函数调用在最终的可执行文件中被正确解析和定位。链接器还会处理来自C标准库或其他第三方库的函数调用,确保所有外部依赖都被正确处理。
链接过程是非常关键的,因为它确保了程序中各个分离编译的部分能够正确地组合在一起,形成一个统一、可执行的整体。这个阶段的错误通常涉及到符号解析失败(比如未定义的引用)或多重定义等问题。通过链接器的工作,最终生成的可执行文件包含了所有必要的代码段和数据段,以及必要的运行时信息,使得程序能够在目标操作系统和硬件上顺利运行。
链接阶段是整个编译过程的集大成者,它将先前的所有工作整合起来,产生最终的成果。这个阶段的高效和准确性对于最终程序的性能和稳定性至关重要。通过理解链接过程,开发者可以更好地理解如何组织和构建他们的C语言项目,以及如何解决编译和链接过程中出现的各种问题。
3.运行环境
在C语言的编译过程中,继翻译环境之后,程序将进入运行环境。这里的运行环境指的是编译好的程序实际执行时所处的环境。这个环境包括操作系统、硬件资源以及程序运行时所需的各种支持和服务。
3.1操作系统的角色
运行环境首先取决于操作系统。不同的操作系统(如Windows、Linux或macOS)提
供了不同的服务和功能,这直接影响程序的执行方式和性能。操作系统负责程序的加载、执行、以及提供程序运行所需的基本服务,如内存分配、文件处理、进程管理等。操作系统还为程序提供了与硬件交互的接口,使得程序能够在特定的硬件配置上运行。
3.2硬件兼容性
运行环境还涉及到硬件层面。不同的处理器架构(如Intel x86, ARM)和不同的硬件配置(如内存大小、处理器速度)都会对程序的运行产生影响。C语言编写的程序在编译时可以进行特定的优化,以适应目标硬件的特性,从而提高运行效率。
3.3运行时库
C语言的运行环境还包括运行时库,这些库提供了标准C库函数的实现,如数学运算、字符串处理、输入输出操作等。这些函数是C语言编程的基础,它们在程序运行时被加载和调用。
3.4环境依赖性
不同的运行环境可能对程序的行为产生影
响。例如,同一程序在不同操作系统或硬件上运行时,可能会因为资源管理策略的差异或系统调用的不同而表现出不同的性能和行为。因此,理解和考虑运行环境的特性在程序设计和优化中是非常重要的。
3.5跨平台运行
对于需要在多种运行环境中工作的C语言程序,考虑跨平台兼容性变得尤为重要。这可能涉及使用条件编译指令来处理不同操作系统的特定代码,或者编写独立于硬件的代码以确保在不同架构上的兼容性。
总而言之,运行环境为C语言程序提供了执行所需的资源和服务,是程序生命周期中不可或缺的一部分。程序员在编写C语言程序时不仅要考虑代码的逻辑和效率,还需要考虑程序将运行在何种环境中,并据此作出适当的设计和调整。这包括对不同操作系统的适应,对硬件资源的合理利用,以及运行时库的有效利用等。通过对运行环境的深入理解,开发者可以更好地优化自己的程序,使之在不同环境下都能高效稳定地运行。
4.结语
理解C语言的编译和链接过程有助于深入了解程序的构建过程。从预处理到编译,再到汇编和链接,每个阶段都是程序转换成可执行文件的重要步骤。通过这些知识,程序员可以更好地优化代码,并有效地解决编译和链接过程中可能出现的问题。
相关文章:

C语言——编译和链接
(图片由AI生成) 0.前言 C语言是最受欢迎的编程语言之一,以其接近硬件的能力和高效性而闻名。理解C语言的编译和链接过程对于深入了解其运行原理至关重要。本文将详细介绍C语言的翻译环境和运行环境,重点关注编译和链接的各个阶段…...

Kubernetes (K8S) 3 小时快速上手 + 实践
1. Kubernetes 简介 k8s即Kubernetes。其为google开发来被用于容器管理的开源应用程序,可帮助创建和管理应用程序的容器化。用一个的例子来描述:"当虚拟化容器Docker有太多要管理的时候,手动管理就会很麻烦,于是我们便可以通…...

如何画出优秀的系统架构图-架构师系列-学习总结
--- 后之视今,亦犹今之视昔! 目录 早期系统架构图 早期系统架构视图 41视图解读 41架构视图缺点 现代系统架构图的指导实践 业务架构 例子 使用场景 画图技巧 客户端架构、前端架构 例子 使用场景 画图技巧 系统架构 例子 定义 使用场…...
VR转接器:打破界限,畅享虚拟现实
你是否曾梦想过踏入另一个世界,体验那种仿佛置身其中的感觉?随着科技的飞速发展,虚拟现实(VR)已经成为了现实。而VR转接器,正是让你畅享虚拟现实的关键所在。 添加图片注释,不超过 140 字&…...

C++学习笔记——用C++实现树(区别于C)
树是一种非常重要的数据结构,它在计算机科学中的应用非常广泛。在本篇博客中,我们将介绍树的基本概念和C中如何实现树。 目录 一、树的基本概念 2.C中实现树 2.1创建一个树的实例,并向其添加节点 2.2三种遍历方式的实现代码 3.与C语言相…...

工业平板定制方案_基于联发科、紫光展锐平台的工业平板电脑方案
工业平板主板采用联发科MT6762平台方案,搭载Android 11.0操作系统, 主频最高2.0GHz,效能有大幅提升;采用12nm先进工艺,具有低功耗高性能的特点。 该工业平板主板搭载了IMG GE8320图形处理器,最高主频为680MHz, 支持108…...
JPA查询PostgreSQL行排序问题
文章目录 问题处理PostgreSQL排序相关JPA相关介绍 问题 我们项目使用Spring Boot构建,使用JHipster生成业务代码,包含基础的增删改查代码使用PostgreSQL作为业务数据库,使用自动生成的JPA构建数据更新语查询在查询某个实体类的列表时&#x…...

【css】渐变效果
css渐变效果 使用 CSS 渐变可以在两种颜色间制造出平滑的渐变效果。 用它代替图片,可以加快页面的载入时间、减小带宽占用。同时,因为渐变是由浏览器直接生成的,它在页面缩放时的效果比图片更好,因此你可以更加灵活、便捷的调整页…...

Maven 依赖传递和冲突、继承和聚合
一、依赖传递和冲突 1.1 Maven 依赖传递特性 1.1.1 概念 假如有三个 Maven 项目 A、B 和 C,其中项目 A 依赖 B,项目 B 依赖 C。那么我们可以说 A 依赖 C。也就是说,依赖的关系为:A—>B—>C, 那么我们执行项目 …...

Linux Centos7静默安装(非图形安装)Oracle RAC 11gR2(Oracle RAC 11.2.0.4)
Oracle RAC (全称Oracle Real Application Clusters )静默安装(非图形安装)教程。 由于这篇文章花费了我太多时间,设置了仅粉丝可见,见谅。 环境说明: 虚拟机软件:VMware Workstation 16 Pro…...
集成开发环境(IDE)介绍
集成开发环境(IDE)介绍 集成开发环境(Integrated Development Environment,IDE)是一种软件应用程序,用于开发和编写软件。常见的IDE包括Eclipse、Visual Studio、IntelliJ IDEA、Qt Creator等。 集成开发环…...

基于物联网设计的智能储物柜(4G+华为云IOT+微信小程序)
一、项目介绍 在游乐场、商场、景区等人流量较大的地方,往往存在用户需要临时存放物品的情况,例如行李箱、外套、购物袋等。为了满足用户的储物需求,并提供更加便捷的服务体验,当前设计了一款物联网智能储物柜。 该智能储物柜通…...
12种常见的网络钓鱼
网络钓鱼是一种网络攻击,是指具有恶意动机的攻击者伪装欺骗人们并收集用户名或密码等敏感信息的一系列行为。由于网络钓鱼涉及心理操纵并依赖于人为失误(而不是硬件或软件漏洞),因此被认定为是一种社会工程攻击。 1. 普通网络钓鱼(群攻&…...

电商物流查询:未来的发展方向
在电商日益繁荣的时代,物流信息查询不仅关乎消费者体验,更影响着电商运营的效率。快速、准确地追踪物流信息至关重要。本文将简述物流信息快速追踪的价值,并重点介绍固乔快递查询助手这一高效查询工具及其批量查询功能。 一、物流信息快速追踪…...
【数据库原理】(25)数据完整性
一.完整性概述 数据库的完整性是保证数据正确性和一致性的关键。它防止数据库中存在不符合业务逻辑或语义规则的数据,避免错误信息的输入和输出。数据库的完整性和安全性不同,安全性关注的是防止非法用户的访问和恶意操作,而完整性则关注数据…...

逻辑运算符
逻辑运算符 什么是逻辑运算符? 在数学中,一个数据x大于5,小于15,我们可以这样来进行表示:5<x<15.在Java中,需要把上面的式子先进行拆解,再进行合并表达。 拆解为:x>5和 x…...
SpringBoot @RequestBody和@ResponseBody注解
1. RequestBody注解 用于将HTTP请求体的内容绑定到方法的参数上。通常情况下,我们使用这个注解来处理POST请求,特别是传递JSON格式的数据。 例: PostMapping("/user") public ResponseEntity<String> createUser(RequestB…...
实验四:静态路由配置
实验四:静态路由配置 1. 静态路由 ( 一般配置 ) 【实验名称】静态路由配置 【实验目的】掌握静态路由的配置方法,理解路由表的作用和原理 【实验设备】路由器( 2 台)、计算机( 2 台)、交叉线…...

UML-类图和类图转化为代码
提示:文章详细的讲解了类图的四种关系,以及每种关系如何转化为对应的代码。 UML-类图和类图转化为代码 一、类于类之间的关系1.依赖关系2.关联关系(1) 单向关联(2) 双向关联(3) 自关联(4) 聚合关联(has-a)(5) 组合关联(contains-a)…...

数据科学与大数据导论期末复习笔记(大数据)
来自于深圳技术大学,此笔记涵盖了期末老师画的重点知识,分享给大家。 等深分箱和等宽分箱的区别:等宽分箱基于数据的范围来划分箱子,每个箱子的宽度相等。等深分箱基于数据的观测值数量来划分箱子,每个箱子包含相同数量…...

使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...

Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...

【VLNs篇】07:NavRL—在动态环境中学习安全飞行
项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战,克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...

LLMs 系列实操科普(1)
写在前面: 本期内容我们继续 Andrej Karpathy 的《How I use LLMs》讲座内容,原视频时长 ~130 分钟,以实操演示主流的一些 LLMs 的使用,由于涉及到实操,实际上并不适合以文字整理,但还是决定尽量整理一份笔…...

从“安全密码”到测试体系:Gitee Test 赋能关键领域软件质量保障
关键领域软件测试的"安全密码":Gitee Test如何破解行业痛点 在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的"神经中枢"。从国防军工到能源电力,从金融交易到交通管控,这些关乎国计民生的关键领域…...

windows系统MySQL安装文档
概览:本文讨论了MySQL的安装、使用过程中涉及的解压、配置、初始化、注册服务、启动、修改密码、登录、退出以及卸载等相关内容,为学习者提供全面的操作指导。关键要点包括: 解压 :下载完成后解压压缩包,得到MySQL 8.…...
在树莓派上添加音频输入设备的几种方法
在树莓派上添加音频输入设备可以通过以下步骤完成,具体方法取决于设备类型(如USB麦克风、3.5mm接口麦克风或HDMI音频输入)。以下是详细指南: 1. 连接音频输入设备 USB麦克风/声卡:直接插入树莓派的USB接口。3.5mm麦克…...

在 Visual Studio Code 中使用驭码 CodeRider 提升开发效率:以冒泡排序为例
目录 前言1 插件安装与配置1.1 安装驭码 CodeRider1.2 初始配置建议 2 示例代码:冒泡排序3 驭码 CodeRider 功能详解3.1 功能概览3.2 代码解释功能3.3 自动注释生成3.4 逻辑修改功能3.5 单元测试自动生成3.6 代码优化建议 4 驭码的实际应用建议5 常见问题与解决建议…...