【C语言基础】:编译和链接(计算机中的翻译官)

文章目录

      • 一、翻译环境和运行环境
        • 1. 翻译环境
          • 1.1 编译
            • 1.1.1 预处理
            • 1.1.2 编译
            • 1.1.3 汇编
          • 1.2 链接
        • 2. 运行环境

一、翻译环境和运行环境

我们在Visual Studio上写的C语言代码其实都是一些文本信息,计算机是不能够直接执行他们的,计算机只能够执行二进制指令。
要想计算机执行我们所写的C语言代码,就需要一个"翻译官",将我们写的C语言代码"翻译"成计算机能够执行的二进制指令。而承当"翻译官"这个角色的就是我们常说的编译器

1. 翻译环境

ANSI C的任何⼀种实现中,存在两个不同的环境。

  1. 第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令(二进制指令)。
  2. 第2种是执行环境,它用于实际执行代码。
1.1 编译

翻译环境是由编译链接两个大的过程组成的,而编译又可以分解成:预处理(有些书也叫预编译)、编译、汇编三个过程。
在这里插入图片描述

编译过程

  • 每个.c源文件都是独立地通过编译器进行编译处理的。编译器会将源代码转换为机器可以理解的中间形式,即目标代码。
  • 在Windows环境下,这些目标代码文件通常具有.obj扩展名;而在Linux环境下,目标文件的扩展名通常是.o。
  • 编译过程包括预处理、编译(语法分析、语义分析、代码生成等)和汇编(将汇编代码转换为机器代码)。

在这里插入图片描述
test.c生成test.objAdd.c生成Add.obj文件,每个C文件都会生成对应的目标文件,每个源文件都是经过编译器单独处理的。多个目标文件通过链接库生成我们的可执行程序。

如果在细分一点的话,编译又可以分解为预处理编译汇编三个过程。

在这里插入图片描述

1.1.1 预处理

在预处理阶段,源文件和头文件会被处理成为.i为后缀的文件。
它主要负责处理源代码中的预处理指令。预处理器是编译器的一个组成部分,它在编译器进行实际编译之前对源代码进行一系列的文本替换和宏替换操作。
在 gcc 环境下想观察⼀下,对 test.c 文件预处理后的.i文件,命令如下:

gcc test.c -E -o test.i

预处理阶段的主要任务包括:

  1. 宏替换(Macro Expansion)
  • 预处理器会处理所有的宏定义,将宏展开成它们所代表的代码。例如,如果定义了一个宏#define PI 3.14159,那么在预处理阶段,所有的PI宏在源代码中都会被替换成3.14159。
  1. 文件包含(File Inclusion)
  • 使用#include指令可以将其他文件的内容包含进来。预处理器会找到这些指定的头文件,并将它们的内容插入到当前文件的相应位置。这使得程序员可以重用代码,例如在多个文件中共享函数声明和类型定义。
  1. 条件编译(Conditional Compilation)
  • 预处理器还处理条件编译指令,如#ifdef、#ifndef、#if、#elif、#else和#endif。这些指令允许程序员根据特定的条件来包含或排除代码块,从而为不同的编译环境定制源代码。
  1. 移除注释(Comment Removal)
  • 预处理器会删除源代码中的注释,因为注释对于编译器来说是无意义的。注释以//或/* … */开始,直到行尾或注释块的结束。
  1. 添加编译器指令(Adding Compiler Directives)
  • 预处理器会添加一些特殊的编译器指令,如行号和文件名,这些信息对于调试程序非常有用。
    处理其他预处理指令:

预处理器还处理一些其他的指令,如#pragma,这些指令通常用于向编译器提供特定的、非标准的指令或请求。
预处理阶段的输出是一个已经经过上述处理的源代码文本,这个文本接下来会被送到编译器的下一阶段——编译阶段。在编译阶段,编译器将对预处理后的代码进行词法分析、语法分析、语义分析等操作,最终生成目标代码或汇编代码。

在这里插入图片描述
在这里插入图片描述
可以看到,生成的test.i文件里面有八百多行代码,我们写的代码才区区几行,前面的几百行代码都是stdio.h这个头文件里面的内容。另外,我们写的宏定义也直接被替换掉了。源代码中的注释也已经被删除。
所以注释是给程序员们看的,而不是给编译器看的。

1.1.2 编译

编译过程就是将预处理后的文件进行⼀系列的:词法分析语法分析语义分析及优化,生成相应的汇编代码文件。
编译过程的命令如下:

gcc test.i -S -o test.s

编译阶段的主要步骤:

以这段代码为例:

array[index] = (index+4)*(2+6);
  1. 词法分析(Lexical Analysis)
  • 编译器首先将预处理后的源代码进行词法分析,这一步骤涉及到将源代码字符串分解成一系列的记号(tokens)。记号是语言中最小的有意义的元素,如关键字、标识符、常量、运算符等。
  • 词法分析器通常会构建一个抽象的记号流,供后续阶段使用。

上面程序进行词法分析后得到了16个记号:
在这里插入图片描述

  1. 语法分析(Syntax Analysis)
  • 语法分析阶段,编译器根据C语言的语法规则检查记号流,构建一棵抽象语法树(AST)。这棵树表示了源代码的层次结构,反映了程序的逻辑组织。
  • 如果源代码不符合语言的语法规则,编译器将在这一阶段报告语法错误。

在这里插入图片描述

  1. 语义分析(Semantic Analysis)
  • 在语义分析阶段,编译器检查AST是否有意义,即检查语义正确性。这包括类型检查、变量声明的一致性、表达式的数据类型是否正确等。
  • 语义分析还会进行符号表的构建,记录变量、函数等的相关信息。

在这里插入图片描述

  1. 中间代码生成(Intermediate Code Generation)

通过上述分析后,编译器会生成中间代码,这种代码是一种独立于机器语言的低级代码,它更加接近于机器指令,但仍然保持了一定的抽象。
中间代码的设计旨在使得代码优化更加容易进行。

  1. 代码优化(Code Optimization)
  • 编译器会对中间代码进行优化,以提高代码的执行效率和减少资源消耗。优化可以在不同的层次进行,包括局部优化、循环优化、数据流分析等。
  • 优化的目标是减少代码的空间和时间复杂度,提高程序的性能。
  1. 目标代码生成(Code Generation)
  • 最后,编译器将优化后的中间代码转换成目标代码,即可以直接在特定硬件和操作系统上执行的机器代码或汇编代码。
  • 目标代码生成阶段需要考虑目标平台的具体指令集和调用约定。

编译阶段是一个复杂的过程,涉及到对源代码的深入理解和转换。编译器的设计和实现需要考虑到语言的特性、目标平台的特点以及程序的性能要求。通过编译阶段,高质量的源代码被转换成有效的机器指令,为最终的程序执行奠定了基础。

在这里插入图片描述

1.1.3 汇编

汇编器是将汇编代码转转变成机器可执行的指令,每⼀个汇编语句几乎都对应一条机器指令。就是根据汇编指令和机器指令的对照表一一的进行翻译,也不做指令优化。

汇编的命令如下:

gcc test.s -c -o test.o
  1. 汇编指令
  • 汇编指令是针对计算机硬件的低级指令,它们通常与机器代码一一对应,但是以一种更易于人类理解和编写的形式表示。
  • 汇编指令包括操作码(opcode)和操作数(operands),操作码指定要执行的操作,操作数提供操作所需的数据或地址。
  1. 地址和数据
  • 汇编器负责将汇编指令中的地址和数据转换为计算机可识别的二进制形式。
  • 这包括对内存地址、寄存器、立即数等的处理和转换。
  1. 符号解析
  • 在汇编代码中,可能会使用标签(labels)和符号(symbols)来引用内存位置或数据。汇编器将这些符号解析为具体的地址或值。
  • 例如,一个标签可能代表一个内存地址,汇编器需要确保所有对该标签的引用都被正确地转换为对应的地址。
  1. 目标文件生成
  • 汇编器处理完所有的汇编指令后,会生成一个目标文件(Object File)。目标文件包含了机器代码和与链接器(Linker)相关的符号信息。
  • 目标文件通常具有特定的格式,如在Windows上通常是.obj文件,在Unix-like系统上通常是.o文件。
  1. 代码优化
  • 虽然主要的优化工作在编译阶段进行,但汇编器也可以执行一些简单的优化,比如消除冗余的指令或改善指令的顺序以提高执行效率。
  1. 依赖处理
  • 汇编器还需要处理源文件中对外部符号的依赖,这些外部符号可能定义在其他汇编源文件或库文件中。
  • 汇编器记录这些依赖关系,并在链接阶段由链接器解决。

在这里插入图片描述

1.2 链接

链接是编译过程的最后一个阶段,它负责将编译阶段生成的一个或多个目标文件与所需的库文件合并,生成最终的可执行文件。链接过程由链接器(Linker)完成,它解决了目标文件之间的相互引用和依赖问题,确保程序中的所有函数和变量引用都能正确地指向它们的实现和定义。

  1. 符号解析(Symbol Resolution)
  • 链接器处理程序中的符号,如函数和全局变量。每个符号都有一个唯一的名称,链接器需要确保每个符号引用都能正确地找到其对应的定义。
  • 当一个目标文件引用了另一个目标文件中的符号时,链接器会找到该符号的定义,并在链接时进行适当的修改。
  1. 地址分配和重定位(Address Assignment and Relocation)
  • 链接器为程序中的所有代码和数据分配内存地址。这个过程涉及到确定每个符号和数据在内存中的确切位置。
  • 重定位是链接过程中的一个关键步骤,它涉及到修改代码中的地址引用,确保它们指向正确的内存位置。这是因为在编译时,编译器并不知道最终的内存布局。
  1. 处理静态和动态库(Static and Dynamic Libraries)
  • 静态库在链接阶段被整合到最终的可执行文件中,成为程序的一部分。这意味着程序运行时不再需要这些库的单独文件。
  • 动态库(或共享库)在程序运行时被加载。它们可以在多个程序之间共享,节省内存和磁盘空间。链接器在链接动态库时,会记录库的路径和所需的符号,以便在运行时找到它们。
  1. 生成可执行文件(Generating the Executable File)
  • 链接器完成所有必要的链接工作后,会生成一个可执行文件。这个文件包含了程序的所有代码、数据、符号表、以及运行时所需的其他信息。
  • 可执行文件的格式依赖于目标操作系统和平台。例如,在Windows上通常是.exe文件,在Linux上通常是没有扩展名的文件。
  1. 处理链接时错误(Link-Time Errors)
  • 如果在链接过程中发现错误,如未定义的符号、多重定义、或者不兼容的库版本,链接器会报告这些错误。程序员需要根据错误信息对代码进行修正,然后重新编译和链接。

【示例】
test.c

#include<stdio.h>
//test.c
//声明外部函数
extern int Add(int, int);
//声明外部的全局变量
extern int g_val;
int main()
{int a = 10;int b = 20;int c = Add(a, b);printf("%d\n", c);return 0;
}

Add.c

int g_val = 2022;
int Add(int x, int y)
{return x + y;
}

在这里插入图片描述
我们已经知道,每个源文件都是单独经过编译器处理生成对应的目标文件。
test.c 经过编译器处理生成 test.o
add.c 经过编译器处理生成 add.o
我们在 test.c 的文件中使用了 add.c 文件中的 Add 函数和 g_val 变量。
我们在 test.c 文件中每⼀次使用 Add 函数和 g_val 的时候必须确切的知道 Add 和 g_val 的地址,但是由于每个文件是单独编译的,在编译器编译 test.c 的时候并不知道 Add 函数和 g_val变量的地址,所以暂时把调用 Add 的指令的目标地址和 g_val 的地址搁置。等待最后链接的时候由链接器根据引用的符号 Add 在其他模块中查找 Add 函数的地址,然后将 test.c 中所有引用到Add 的指令重新修正,让他们的目标地址为真正的 Add 函数的地址,对于全局变量 g_val 也是类似的方法来修正地址。这个地址修正的过程也被叫做:重定位

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

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

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

相关文章

第6章 6.4.1 案例一:爬取亚洲各地区的实时时间(MATLAB入门课程)

讲解视频&#xff1a;可以在bilibili搜索《MATLAB教程新手入门篇——数学建模清风主讲》。​ MATLAB教程新手入门篇&#xff08;数学建模清风主讲&#xff0c;适合零基础同学观看&#xff09;_哔哩哔哩_bilibili 本案例将引导大家爬取亚洲各地区的实时时间。我们将从下面这个网…

第 128 场 LeetCode 双周赛题解

A 字符串的分数 模拟 class Solution {public:int scoreOfString(string s) {int res 0;for (int i 1; i < s.size(); i) res abs(s[i] - s[i - 1]);return res;} };B 覆盖所有点的最少矩形数目 排序&#xff1a;先按照 x i x_i xi​ 排序&#xff0c;然后顺序遍…

【C++庖丁解牛】底层为红黑树结构的关联式容器--哈希容器(unordered_map和unordered_set)

&#x1f341;你好&#xff0c;我是 RO-BERRY &#x1f4d7; 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f384;感谢你的陪伴与支持 &#xff0c;故事既有了开头&#xff0c;就要画上一个完美的句号&#xff0c;让我们一起加油 目录 1. unordered系列关联式容…

系统架构设计图

首先明确应用架构的定义&#xff0c;从百度百科上即可了解到何为应用架构&#xff1a; 应用架构&#xff08;Application Architecture&#xff09;是描述了IT系统功能和技术实现的内容。应用架构分为以下两个不同的层次&#xff1a; 企业级的应用架构&#xff1a;企业层面的应…

arm工作模式、arm9通用寄存器、异常向量表中irq的异常向量、cpsr中的哪几位是用来设置工作模式以及r13,r14,15别名是什么?有什么作用?

ARM 首先先介绍一下ARM公司。 ARM成立于1990年11月&#xff0c;前身为Acorn计算机公司 主要设计ARM系列RISC处理器内核 授权ARM内核给生产和销售半导体的合作伙伴ARM公司不生产芯片 提供基于ARM架构的开发设计技术软件工具评估版调试工具应用软件总线架构外围设备单元等等CPU中…

【MySQL】MySQL在Centos 7环境安装

目录 准备工作 第一步&#xff1a;卸载不要的环境 第二步&#xff1a;下载官方的mysql 第三步 上传到Linux中 第四步 安装 正式安装 启动 ​编辑 登录 准备工作 第一步&#xff1a;卸载不要的环境 使用root进行安装 如果是普通用户&#xff0c;使用 su - 命令&#…

langchain RunableBranch 分类判断选择不同链

import os from dotenv import load_dotenv from langchain_community.llms import Tongyi load_dotenv(key.env) # 指定加载 env 文件 key os.getenv(DASHSCOPE_API_KEY) # 获得指定环境变量 DASHSCOPE_API_KEY os.environ["DASHSCOPE_API_KEY"] # 获得指定环境…

字符串转换为List<String>时候抛出异常:com.alibaba.fastjson2.JSONException: TODO : s

前言&#xff1a; 一个字符串想要能够转换为 List&#xff0c;其本身必须是具备 List 属性的字符串。 问题现象&#xff1a; 项目中需要对第三方接口返回的字符串转换为 List&#xff0c;就想到了使用 fastjson 来处理。 代码如下&#xff1a; Object obj data.get(SignC…

格式化D盘后C盘内的文件会受影响吗?深度解析

在计算机的日常使用中&#xff0c;磁盘格式化是一个常见的操作&#xff0c;它能帮助我们清除磁盘上的数据&#xff0c;为新的数据腾出空间。然而&#xff0c;当涉及到系统盘和其他存储盘时&#xff0c;许多用户会担心一个问题&#xff1a;如果我格式化了非系统盘&#xff0c;比…

蓝桥杯真题演练:2023B组c/c++

日期统计 小蓝现在有一个长度为 100 的数组&#xff0c;数组中的每个元素的值都在 0 到 9 的范围之内。 数组中的元素从左至右如下所示&#xff1a; 5 6 8 6 9 1 6 1 2 4 9 1 9 8 2 3 6 4 7 7 5 9 5 0 3 8 7 5 8 1 5 8 6 1 8 3 0 3 7 9 2 7 0 5 8 8 5 7 0 9 9 1 9 4 4 6 8 6 3 …

连锁品牌企业为何不能使用通用收银系统?

在连锁服装品牌企业中&#xff0c;加盟连锁店往往选择使用特定的收银管理系统&#xff0c;而不是市面上通用的收银系统。这一选择背后有着几个主要原因&#xff1a; 首先&#xff0c;加盟连锁店的核心在于品牌的一致性和管理。采用统一的收银管理系统可以确保所有门店在运营和管…

2024连锁收银系统哪个好 有什么特点

在服装连锁店的经营中&#xff0c;选择一款优秀的收银系统至关重要。收银系统不仅仅是简单的结账工具&#xff0c;更是管理销售、库存和客户信息的关键平台。以下将介绍几款优秀的服装连锁店收银系统&#xff0c;以便您更好地了解各款系统的特点和优势。 1. 商淘云连锁店收银系…

【CSS】一篇文章讲清楚screen、window和html元素的位置:top、left、width、height

一个Web网页从内到外的顺序是&#xff1a; 元素div,ul,table... → 页面body → 浏览器window → 屏幕screen 分类详情屏幕screen srceen.width - 屏幕的宽度 screen.height - 屏幕的高度&#xff08;屏幕未缩放时&#xff0c;表示屏幕分辨率&#xff09; screen.availLeft …

中国绿色技术助力全球能源转型(国际论坛)

中国的清洁能源发展战略和实践对全球能源结构转型产生了深远影响。作为全球最大的可再生能源生产和消费国&#xff0c;中国在推动国内可再生能源产业发展的同时&#xff0c;也积极与世界各国分享技术和经验&#xff0c;促进全球范围内清洁能源技术的普及和应用成本的降低。例如…

C 408—《数据结构》易错考点200题(含解析)

目录 Δ前言 一、绪论 1.1 数据结构的基本概念 : 1.2 算法和算法评价 : 二、线性表 2.2 线性表的顺序表示 : 2.3 线性表的链式表示 : 三、栈、队列和数组 3.1 栈 3.2 队列 3.3 栈和队列的应用 3.4 数组和特殊矩阵 四、串 4.2 串的模式匹配 五、树与二叉树 5.1 树的基…

解决 Mac App Store 不显示可用更新的问题

相信不少人遇到过 Mac App Store 有更新提示&#xff0c;但进入应用商店的「更新」一栏不会显示可用更新的软件列表。只有进入对应软件详情页才会显示可更新。 解决方法是使用快捷键「⌘ R」来刷新页面。 Related Link: Troubleshooting App Store Issues

Docker 安装 RocketMQ

目录 一、新建两个配置文件 1.1 创建docker-compose.yml文件 1.2 .新建broker.conf文件 二、运行 三、可视化界面 一、新建两个配置文件 1.1 创建docker-compose.yml文件 version: 3.5 services:rmqnamesrv:image: foxiswho/rocketmq:servercontainer_name: rmqnamesrvports…

Gradle 实战 - 命令行传递-ApiHug准备-工具篇-013

&#x1f917; ApiHug {Postman|Swagger|Api...} 快↑ 准√ 省↓ GitHub - apihug/apihug.com: All abou the Apihug apihug.com: 有爱&#xff0c;有温度&#xff0c;有质量&#xff0c;有信任ApiHug - API design Copilot - IntelliJ IDEs Plugin | Marketplace ApiHug …

人工智能科普:人工智能的分类

人工智能的分类多种多样&#xff0c;根据不同的标准和应用场景&#xff0c;可以将其划分为多个不同的类别。以下是对人工智能分类的详细探讨。 一、按应用领域分类 1. 智能机器人&#xff1a;智能机器人是人工智能技术在机器人领域的应用。它们能够根据环境和任务的不同进行自…

【论文速读】| 基于大语言模型的模糊测试技术

本次分享论文为&#xff1a;Large Language Models Based Fuzzing Techniques: A Survey 基本信息 原文作者&#xff1a;Linghan Huang, Peizhou Zhao, Huaming Chen, Lei Ma 作者单位&#xff1a;悉尼大学&#xff1b;东京大学&#xff1b;阿尔伯塔大学 关键词&#xff1a;…