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)。这些词法单元包括关键字(如ifwhile)、标识符(如变量和函数名)、常量、字符串字面量和符号(如+-*/)等。

词法分析的主要任务是识别出源代码中的各种基本元素,并去除空白字符、换行符等无关内容,为后续的语法分析阶段提供清晰、简化的输入。

在词法分析阶段,编译器将这行代码分解为一系列词法单元(tokens)。这个过程大致如下:

  1. int - 关键字,表示整数类型。
  2. a - 标识符,代表变量名。
  3. = - 运算符,表示赋值。
  4. x - 标识符,代表变量名。
  5. > - 运算符,表示大于比较。
  6. y - 标识符,代表变量名。
  7. ? - 运算符,表示条件表达式的开始。
  8. x - 标识符,代表变量名。
  9. : - 运算符,用于条件表达式,区分不同的输出。
  10. y - 标识符,代表变量名。
  11. ; - 分号,表示语句结束。
2.2.2语法分析

接下来的语法分析阶段,编译器使用词法分析得到的词法单元来构建抽象语法树(Abstract Syntax Tree,AST)。在这个过程中,编译器检查代码是否遵循C语言的语法规则。语法分析器需要识别各种语法结构,如表达式、语句、函数定义等,并确保它们正确地组合在一起。

如果代码中存在语法错误,如缺少分号、括号不匹配等,语法分析器会在这个阶段发现并报告这些错误。语法分析是确保程序结构正确的重要步骤。

在语法分析阶段,编译器使用上述词法单元来构建抽象语法树(AST)。这个代码段大致对应于以下结构:

  • 声明语句
    • 类型: int
    • 变量: a
    • 赋值表达式
      • 左边: 变量 a
      • 右边: 条件表达式
        • 条件部分: 比较表达式 (x > y)
        • 真值部分: 变量 x
        • 假值部分: 变量 y
2.2.3语义分析

最后,编译过程进入语义分析阶段。在这个阶段,编译器检查源代码的语义正确性,确保程序中的每个操作都是有意义的。语义分析包括变量和函数的声明检查、类型检查、表达式中运算符的有效性检查等。

例如,编译器会检查变量是否在使用前已被声明,函数调用是否与函数定义匹配,以及表达式中是否存在类型不兼容的情况。语义分析是保证程序行为符合预期的关键步骤。

在语义分析阶段,编译器检查代码的语义正确性。针对这段代码,编译器将执行以下操作:

  • 确认 xy 已被声明并定义(如果之前没有声明,这将是一个语义错误)。
  • 确认 xy 的类型可以进行 > 比较操作。
  • 确认条件表达式的两个输出(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链接的主要任务
  1. 解析符号:链接器首先解析出程序中的所有符号,如函数和变量名。它需要处理的主要问题是,找出每个符号的定义,并将其与引用该符号的地方连接起来。

  2. 地址和空间分配:链接器分配内存地址给各个程序段和变量。它会根据每个目标文件的相对地址信息,计算出实际运行时的绝对地址。

  3. 解决外部依赖:链接器会处理目标文件和库文件之间的依赖关系,例如,如果你的程序调用了标准库函数,链接器会从标准库中找到这些函数的实现,并将其与你的代码相连接。

  4. 生成可执行文件:最终,链接器生成一个可执行文件,这个文件包含了所有必要的代码和数据,以便在目标平台上运行。

2.4.2实例

假设你有两个C文件:main.cfunctions.c

  • main.c 包含主函数和对functions.c中定义的函数的调用。
  • functions.c 包含一些定义的函数。
2.4.3步骤
  1. 编译:首先,使用编译器(如gcc)分别编译这两个文件,生成两个目标文件。
  2. 链接:然后,将这些目标文件链接成一个可执行文件。
//1.编译
gcc -c main.c -o main.o
gcc -c functions.c -o functions.o

这将分别为 main.cfunctions.c 生成 main.ofunctions.o 目标文件。

//2.链接
gcc main.o functions.o -o program
  1. 这个命令会将 main.ofunctions.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语言的编译和链接过程有助于深入了解程序的构建过程。从预处理到编译,再到汇编和链接,每个阶段都是程序转换成可执行文件的重要步骤。通过这些知识,程序员可以更好地优化代码,并有效地解决编译和链接过程中可能出现的问题。

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

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

相关文章

Architecture Lab:预备知识2【汇编call/leave/ret指令、CS:APP练习4.4】

chap4的练习4.4&#xff08;page.255&#xff09;让用Y86-64实现rsum&#xff08;递归求数组元素之和&#xff09;&#xff0c;提示为&#xff1a;先得到x86-64汇编代码&#xff0c;然后转换成Y86-64的 这是rsum的c实现&#xff1a; long rsum(long *start, long count) {if …

【面试合集】说说微信小程序的发布流程?

面试官&#xff1a;说说微信小程序的发布流程&#xff1f; 一、背景 在中大型的公司里&#xff0c;人员的分工非常仔细&#xff0c;一般会有不同岗位角色的员工同时参与同一个小程序项目。为此&#xff0c;小程序平台设计了不同的权限管理使得项目管理者可以更加高效管理整个团…

微软推出付费版Copilot

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 微软已经超越苹果&#xff0c;成了全球市值最高的公司&#xff0c;其他公司都因为AI大裁员&#xff0c;而微软正好相反&#xff0c;当然这个原因很简单&#xff1a;就是微软强制把AI全面接入到系统里来了。而Copilot…

Mac系统下,保姆级Jenkins自动化部署Android

一、Jenkins自动化部署 1、安装jenkins 官网&#xff1a;macOS Installers for Jenkins LTS 选择macOS brew install jenkins-lts 安装最新: brew install jenkins-lts 启动jenkins服务: brew services start jenkins-lts 重启jenkins服务: brew services restart jenkin…

web开发学习笔记(2.js)

1.引入 2.js的两种引入方式 3.输出语句 4.全等运算符 5.定义函数 6.数组 7.数组属性 8.字符串对象的对应方法 9.自定义对象 10.json对象 11.bom属性 12.window属性 13.定时刷新时间 14.跳转网址 15.DOM文档对象模型 16.获取DOM对象&#xff0c;根据DOM对象来操作网页 如下图…

基于杂交PSO算法的风光储微网日前优化调度(MATLAB实现)

微网中包含&#xff1a;风电、光伏、储能、微型燃气轮机&#xff0c;以最小化电网购电成本、光伏风机的维护成本、蓄电池充放电维护成本、燃气轮机运行成本及污染气体治理成本为目标&#xff0c;综合考虑&#xff1a;功率平衡约束、燃气轮机爬坡约束、电网交换功率约束、储能装…

【GCC】6 接收端实现:周期构造RTCP反馈包

基于m98代码。GCC涉及的代码,可能位于:webrtc/modules/remote_bitrate_estimator webrtc/modules/congestion_controller webrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback.cc webrtc 之 RemoteEstimatorProxy 对 remote_bitrate_estimator 的 RemoteEstimato…

Vue 富文本实现内容项目倒序

应用场景&#xff1a; 比如写计划和待办事项&#xff0c;内容少还好&#xff0c;内容多了最新的内容就放在下面了&#xff0c;每次打开要滚动到最后才能看到&#xff0c;这时可以使用倒序把最新的排在最前面。 倒序前&#xff1a; 倒序后&#xff1a; 倒序代码&#xff1a; …

设计模式⑥ :访问数据结构

文章目录 一、前言二、Visitor 模式1. 介绍2. 应用3. 总结 三、Chain of Responsibility 模式1. 介绍2. 应用3. 总结 参考内容 一、前言 有时候不想动脑子&#xff0c;就懒得看源码又不像浪费时间所以会看看书&#xff0c;但是又记不住&#xff0c;所以决定开始写"抄书&q…

ElasticSearch概述+SpringBoot 集成ES

ES概述 开源的、高扩展的、分布式全文检索引擎【站内搜索】 解决问题 1.搜索词是一个整体时&#xff0c;不能拆分&#xff08;mysql整体连续&#xff09; 2.效率会低&#xff0c;不会用到索引&#xff08;mysql索引失效&#xff09; 解决方式 进行数据的存储&#xff08;只存储…

【51单片机系列】继电器使用

文章来源&#xff1a;《零起点学Proteus单片机仿真技术》。 本文是关于继电器使用相关内容。 继电器广泛应用在工业控制中&#xff0c;通过继电器对其他大电流的电器进行控制。 继电器控制原理图如下。继电器部分包括控制线圈和3个引脚&#xff0c;A引脚接电源&#xff0c;B引…

排序算法9----计数排序(C)

计数排序是一种非比较排序&#xff0c;不比较大小 。 1、思想 计数排序又称为鸽巢原理&#xff0c;是对哈希直接定址法的变形应用。 2、步骤 1、统计数据&#xff1a;统计每个数据出现了多少次。&#xff08;建立一个count数组&#xff0c;范围从[MIN,MAX],MAX代表arr中…

.Net 8.0 Web API Controllers 添加到 windows 服务

示例源码下载&#xff1a;https://download.csdn.net/download/hefeng_aspnet/88747022 创建 Windows 服务的方法之一是从工作线程服务模板开始。 但是&#xff0c;如果您希望能够让它托管 API 控制器&#xff08;也许是为了查看它正在运行的进程的状态&#xff09;&#xff0…

深入浅出Spring AOP

第1章&#xff1a;引言 大家好&#xff0c;我是小黑&#xff0c;咱们今天要聊的是Java中Spring框架的AOP&#xff08;面向切面编程&#xff09;。对于程序员来说&#xff0c;理解AOP对于掌握Spring框架来说是超级关键的。它像是魔法一样&#xff0c;能让咱们在不改变原有代码的…

git基础知识

简述 git 的安装配置、工作区域划分、文件类型、基本命令。 基础安装与配置 基于 WSL 的 Ubuntu 下的 git 打开或关闭Windows功能->Hyper-V、Virtual Machine Platform、Windows Subsystem for Linux # 1.必须运行 Windows 10 版本 2004 及更高版本&#xff08;内部版本 …

matplotlib绘制动态瀑布图

绘制瀑布图思路&#xff1a;遍历指定文件目录下所有的csv文件&#xff0c;每读一个文件&#xff0c;取文件前20行数据进行保存&#xff0c;如果超过规定的行数300行&#xff0c;将最旧的数据删除&#xff0c;仅保留300行数据进行展示。 网上找的大部分绘制瀑布图的代码&#x…

Visual Studio 2022 成功配置QT5.12.10

目录 下载并安装Visual Studio 2022 Qt5.12.10下载 Qt5.12.10安装 Qt VS Tools for Visual Studio 2022下载 Visual Studio 2022配置 测试 下载并安装Visual Studio 2022 下载社区版并安装&#xff0c;这个比较快。 Qt5.12.10下载 官网下载很慢&#xff0c;还不如百度网…

LLM:Training Compute-Optimal Large Language Models

论文&#xff1a;https://arxiv.org/pdf/2203.15556.pdf 发表&#xff1a;2022 前文回顾&#xff1a; OpenAI在2020年提出《Scaling Laws for Neural Language Models》&#xff1a;Scaling Laws(缩放法则&#xff09;也一直影响了后续大模型的训练。其给出的结论是最佳计算效…

day20【LeetCode力扣】142.环形链表Ⅱ

day20【LeetCode力扣】142.环形链表Ⅱ 1.题目描述 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了…

STM32 TIM输出比较、PWM波形

单片机学习&#xff01; 目录 一、输出比较简介 二、PWM简介 三、输出比较通道 3.1通用定时器的输出比较部分电路 3.2高级定时器的输出比较部分电路 四、输出模式控制器 五、PWM基本结构 六、PWM参数计算 总结 前言 文章讲述STM32定时器的输出比较功能&#xff0c;它主…