C/C++程序编译与链接(一) 编译与链接的概念

C语言编程全过程 <br /><br /> C语言编程全过程<br />       C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接。编译就是把文本形式源代码翻译为机器语言形式的目标文件的过程。链接是把目标文件、操作系统的启动代码和用到的库文件进行组织形成最终生成可执行代码的过程。过程图解如下:<br /><br /><br />        从图上可以看到,整个代码的编译过程分为编译和链接两个过程,编译对应图中的大括号括起的部分,其余则为链接过程。<b 阅读详情


python,js等语言,编译,链接过程对程序员来说是完全透明的,代码写完就可以直接跑起来,错误会在运行过程中发现。

而对C++来说,代码写完到程序运行中间还隔了一大步,就是编译与链接。它们对程序员来说并不是透明的,需要根据需求来控制编译,链接的过程,控制可执行文件的生成。

大部分C++程序员都遇到过诸如:符号未定义,重复的符号定义,动态库依赖问题,依赖库生成的规则不同导致的符号冲突,如果不了解编译与链接的基本知识和规则,解决起来还是挺麻烦的,只能头疼医头,脚痛医脚。

这将是一系列文章,介绍编译,链接的基本概念及流程,加载器的规则,相关工具的使用。

本篇是介绍编译与链接的基本概念和流程。

首先编译器负责编译与链接,将一系列源文件生成可运行的程序。

常用的编译器包括GCC,Vistual Studio,分别对应了Linux和Windows平台(当然GCC也可以运行于Windows下)。

编译器生成可执行文件的分为两步:编译与链接,编译将源文件编译成目标文件;链接器将目标文件及程序依赖的库链接成可执行文件,Linux中的连接器是ld程序。

编译

编译的目标就是将源文件变成目标文件,包括预处理,语言分析,汇编,生成目标文件:

预处理

预处理就是替换原文件中宏。

在C/C++程序中,宏会被大量使用,它可以用于区分不同平台的条件编译,也可以用于简化代码。

对宏的处理是在编译的第一个阶段,经过预处理器的处理,源文件中的宏被替换成定义内容,它的规则如下:

  • 将#include关键字标示的含有定义的文件包含到源代码文件中。
  • 将#define语句指定的值转换成常量。
  • 在代码中调用宏的位置将宏定义转换成代码。
  • 根据#if,#elif和#endif指令的位置包含或排除特定部分的代码。

在gcc中,我们可以用如下命令生成宏被替换后的代码。
gcc -E -P <input file> -o <output preprocessed file>.i

这个命令只是告诉gcc只做预处理,并没有编译,如果我们遇到一个比较复杂的宏拿不准解析后的具体代码,可以将宏单独摘出来放到文件中,也不必在意语法是否正确。通过此命令可以看到被解析成的具体代码。

比如下面的代码 :

#define _SUM_(a,b) (a) + (b)
void function()
{
    _SUM_(2,3);
}

gcc -E -P sum.cpp -o sum.i 处理后,变为:

void function()
{
    (2) + (3);
}

跨平台中代码中最常用的方式,就是通过#if #define宏来区分代码,在预处理阶段满足宏条件的代码将会保留。

语言分析阶段

在这个阶段中,编译器会将C/C++代码换成更易于处理的形式(删去注释和不必要的空格,以及从文本中提取符号等操作)。通过词法分析,就可以得到这种优化和精简后的源代码形式,词法分析的目的在于检查程序是否满足编程语言的语法规则。编译器会在检查到不满足语法规则错误的时候报告错误或发出警告。编译错误会导致编译过程中断。
可以更加细分为三个阶段:

  • 词法分析
  • 语法分析
  • 语义分析

如果编译报错就是属于这个阶段。

汇编阶段

当源代码经过校验,其中不包含任何语法错误后,编译器才会执行汇编阶段。在这个阶段中,编译器会将标准的语言集合转换成特定CPU指令集。不同的CPU包含不同的功能需求,通常会包含不同的指令集,寄存器和中断。这就是为什么不同的处理器要有不同的编译器对其支持。

gcc 可以通过下面的命令将源代码转换成对应的ASCII编码的文本文件

**gcc -S -masm=intel function.c -o function.s**

-masm=intel表示生成intel平台汇编代码。

优化阶段

当源代码文件生成最初版本的汇编代码后,优化过程就开始了,这可以将程序的寄存器使用率最小化。此外,通过分析能够预测出实际上不需要执行的部分代码,并将其删去。

目标文件

编译阶段的产物就是目标文件,目标文件是二进制文件,它们有标准格式,能让操作系统认识它。在Visual Studio(windows)下目标文件是以.obj结尾的文件,在GCC下目标文件是以.o结尾的问题。

每个源文件都生成一个对应的目标文件(也称一个编译单元,汇编指令会在此阶段转换成对应机器指令的二进制值,并写入目标文件的特定位置)。

源文件中的方法和变量名就在目标文件中就称为符号。其实我们只要知道目标文件中包括各种不同类型(主要指作用域)的符号就足够了,有关目标文件中的绝大多数细节,我们都不用关心。大体上目标文件包括三种类型符合:

  • 导出符号,可以供其他编译单元(目标文件)使用。
  • 局部符号,仅供当前编译单元(目标文件)使用。
  • 未定义的符号,在其他编译单元中(目标文件),本质上是无法确定该符号的地址。

一个目标文件形象的如下图:

编译器视角的目标文件.png

我们在编译程序,我们通常需要关注其中的符号,因为我们遇到的编译的多数错误就是符号未定义或是符号重定义。可以通过nm命令查看目标文件中的符号,如下 function_test.cpp文件生成的目文件中的符号:

int addNum(int a,int b) {
    return a+b;
}

int subtractNum(int a,int b) {
    return a-b;
}

gcc -c function_test.cpp,生成function_test.o文件,通过nm 命令查看包含的符号

nm function_test.o

0000000000000014 T _Z11subtractNumii
0000000000000000 T _Z6addNumii

如果是以.c结尾的源文件文件,则符号名称如下:

0000000000000000 T addNum
0000000000000014 T subtractNum

这是因为对C++和C文件,gcc对符号的命名规则不同。

链接

经过编译阶段后,源文件变为了一个一个目标文件(一个个独立的编译单元)。链接阶段就是将这些目标文件生成可执行文件。

源文件中相互调用的方法,变量,在编译阶段并不知道它们具体的地址,可以说编译阶段视角只是局限在单个文件内,外部的方法和变量,并不知道它们在哪。那么目标文件中就是这些方法或变量的地址无法知道。但是完整的程序这些地址是一定需要的,那么这个建立地址联系的工作就是链接阶段完成,经常出现的符号未定义或符号重复定义就是这个阶段产生的错误。

链接器的输入是一系列目标文件,主要工作就是给这些目标文件中相互调用的符号生成正确的地址,再生成正确的可执行文件。它的工作类似于拼积木,如下:

拼接.png

它包括如下几个步骤:

  1. 重定位

链接过程的第一个阶段仅仅进行拼接,其过程是将分散在单独目标文件中不同类型的节拼接到程序内存映射节中。

  1. 解析引用
  • 检查拼接到程序内存映射中的节。
  • 找出哪些部分代码产生了外部调用。
  • 计算该引用的精确地址(在内存映射中的地址)
  • 最后,将机器指令中的伪地址替换成程序内存映射的实际地址,这样就完成了引用解析。

连接器区别于编译器,它并不关心编写的代码的任何细节。相反,链接器关注的是目标文件的集合,并致力于将这些目标文件拼接成程序内存映射。

如下是链接器视角下的目标文件,根据它们的导出的符号(在源文件已经实现了/定义了的函数或变量)和它们需要的符号(调用/使用其它源文件中的函数或变量)进行拼接(确认符号地址)成完整的可执行文件。

链接器视角下的目标文件.png

结语

编译器本质上是将源代码中的高级语言翻译成低级语言,比如翻译成intel x86平台的汇编代码。上面简单的描述编译器的主要工作后,可以体会到编译器的复杂性,通常一个大型的C/C++项目有成千上万上个源文件,源文件间的调用关系错中复杂,编译器生成正确的汇编代码,要理解这些调用关系产生正确符号地址等,再生成正确的执行文件,还要讲究速度。这样简单的想想都很复杂。

平常我们认为C/C++复杂,认为它们是比较低级的语言,但是在它们背后,编译器还是做了大量的工作。

那么对python,java这样简洁,高效的语言。可想而知,它们背后的编译器系统更加强大,承担了更多工作。

所谓的哪有什么轻松,简单,只是别人在替你负重前行。

编译连接的四步 1.gcc,g++安装 2.编译链接的四步 (1)预编译: gcc -E main.c -o main.i (2)编译 gcc -S main.i -o main.s (3)汇编 gcc -c main.s -o main.o (4)链接 gcc main.o -o main 执行: ./main 或者:全路径/main 3.编译链接的过程 main.c 通过预编译 生成main.i文件 main.i 通过编译生成main.s 文件 main.s 通过汇编生成main.o 文件 链.. 阅读详情

相关推荐

Linux 下 C/C++ 程序编译的过程

本文将介绍如何将 C/C++ 语言编写的程序转换成为处理器能够执行的二进制代码的过程,包括四个步骤:预处理()编译()汇编()链接(在此之前,首先来看一下 GCC 工具链。

Projectsauron 的博客 3万+

什么是编译和链接(通俗易懂,深入本质)

我们平时所说的程序,是指双击后就可以直接运行的程序,这样的程序被称为可执行程序(Executable program)。

qq_64844180的博客 1万+

Windows下使用MinGW在命令行编译运行C/C++程序

Windows下使用MinGW在命令行编译运行C/C++程序 一.安装编译器 首先你需要下载一个编译器,我选择的是GNU的mingw,附上下载地址 https://sourceforge.net/projects/mingw/ 最开始选择的是在线安装,但是因为网速太慢,安装速度感人,所以最后下载了压缩包。将压缩包解压,然后在系统环境变量中配置一下bin目录的路径即可。在命令行中使用命令...

涔曦的博客 2万+

编译链接

1,编译链接 编译链接分为4个步骤:预处理、编译、汇编、链接 2,预处理(预编译) 预编译过程把源文件变成.i文件,主要处理以#开头的预编译指令,比如#include、#define等 主要处理规则: 2,编译 编译是把预处理完的文件进行一系列词法分析、语法分析、语义分析、优化后,生成相应的汇编代码文件。 3,汇编 汇编器将汇编代码变成机器指令,没有复杂语法,也没有语义,也不需要优化,只需要一一对应的翻译即可。 4,链接 链接过程生成可执行文件。 ...

nameofcsdn的博客 8567

C/C++编译链接过程

使用#define进行宏定义宏定义是一种预处理命令,可以用来定义常数、表达式、字符串等。例如:#define PI3.14159宏定义可以是简单的常量值定义,也可以是带参数的宏定义。使用条件编译指令条件编译指令包括#if#ifdef#ifndef#elif和#endif等。这些指令用于根据特定条件决定是否包含某段代码。#ifdef:检查某个宏是否被定义。#ifndef:检查某个宏是否未被定义。#if:根据表达式的值或某个特定宏是否被定义来确定编译条件。示例:#endif。

如无必要,勿增实体。 1966

C/C++的编译和链接过程

cl.exe和link.exe分别是visual studio 中的编译器和链接器 配置属性中的【c/c++】(设置编译的一些选项) 和 【链接器】选项页中,最后的那个命令行汇总了所有生效的设置,就是最终执行的命令行 配置属性中的VC++ 目录用于设置各个路径,相当于设置环境变量PATH(搜可执行文件的路径): INCLUDE(搜include中文件的路径) LIBPATH(搜使用usin...

u012138730的专栏 8883

编译和链接(超详细)

🔹分享c语言知识及代码。

wcblh的博客 2069

2025软考中级《数据库系统工程师》案例模拟题合集

1.【说明】某销售企业正在实施电商平台项目,主要功能模块包括用户中心、商品中心、交易中心、支付中心、营销中心等。其中支付中心模块包括了一个账户表:Account (ano, aname, balance),其中属性含义分别为:账户号,账户名称和账户余额。 假设现在存在事务Ti对账户表Account进行余额的修改,系统记录了修改账户表的日志记录,假设更新日志记录表示为 <Ti,A,V1,V2>,表明事务Ti对账户A执行了一个写操作,写操作前账户A的余额是V1,写操作后账户A的余额为V2。<Ti,D

2301_76902257的博客 2327

Linux中C/C++程序编译过程与动静态链接库概述

在云服务器上,如果默认没有安装 C/C++ 的静态库和编译工具,您可以通过相应的包管理器手动安装所需的工具和库。安装完成后,您就可以开始编写和编译 C/C++ 程序了。

Srlua的博客 1914

MATLAB绘图中文乱码终极解决方案:从方框到清晰显示的全面指南

本文提供了MATLAB绘图中文乱码的全面解决方案。针对中文显示为方框的问题,从单次绘图的临时字体设置,到通过修改默认配置实现一劳永逸,再到系统级字体排查与Java环境配置,给出了详细的步骤指南。文章还涵盖了字体名称指定、图像导出防丢失以及跨平台协作等高级技巧,帮助用户彻底解决中文乱码困扰。

weixin_29093017的博客 614

别再傻傻分不清了!Anaconda和Miniconda到底选哪个?附保姆级安装与镜像源配置(Windows/Mac)

本文详细解析了Anaconda和Miniconda的核心差异,包括体积、预装内容、系统资源占用等关键维度,并提供了针对不同需求的决策框架。附有Windows和Mac双平台的详细安装与配置指南,帮助用户根据实际需求选择最适合的conda工具,提升开发效率。

weixin_30315723的博客 426

C/C++程序的(编译,链接)翻译与运行

c/c++程序的翻译环境与运行环境

2301_79698419的博客 1675

STM32F4 SPI FLASH(W25Q128)驱动详解(含DMA+完整读写擦除+低功耗)

本文实现了一款基于STM32 HAL库的SPI Flash驱动,以W25Q128为例,提供了完整的初始化、读写、擦除、ID读取等功能。驱动集成了DMA传输、超时保护、页对齐写入等特性,支持扇区擦除、整片擦除、页写入及跨页缓冲写入。代码结构清晰,易于移植到其他SPI Flash型号(如W25Q64、W25Q16等),适用于数据存储、固件升级、文件系统等嵌入式场景。

kevin1499的博客 96

C/C++ 编译、链接和运行

文件中的符号和调试信息可以用于调试和分析程序。如果需要生成发布版本的程序,可以使用strip命令删除这些信息,以减小程序的大小。

m0_51073072的博客 2130

数据挖掘实战-基于KMeans算法对超市客户进行聚类分群

本实验基于KMeans算法对超市客户进行了聚类分群。通过对客户购物数据进行聚类,我们成功将客户分为不同的群体。每个群体代表了具有相似购物行为和偏好的客户群体。通过实验结果,我们发现了客户群体之间的明显差异和共性。不同群体的客户在购买频率、购物金额和购买种类等方面有所区别。这为超市提供了重要的市场细分信息,帮助超市经营者更好地了解不同客户群体的需求和喜好,制定有针对性的营销策略和商品推荐,提高客户满意度和忠诚度。此外,实验还揭示了超市客户的购物行为规律,为超市优化商品陈列和促销策略提供了指导。

m0_64336780的博客 2万+

NLTK中WordNet与同义词处理

通过本教程,学习了如何使用NLTK中的WordNet进行自然语言处理任务。首先介绍了WordNet的基本概念及其在NLTK中的集成使用,接着讲解了如何查找同义词、反义词等词汇关系,并结合实际应用演示了语义相似性计算。这些技能在文本分析、情感分析、信息检索等场景中有广泛应用,可以帮助开发者构建更智能的自然语言处理系统。掌握这些基本操作后,可以进一步探索WordNet的其他高级功能,结合其他NLTK工具进行更复杂的语言分析。

Mr数据杨 471

【使用命令行编译 C/C++ 程序的完整指南】

cl.exe是Microsoft Visual C++ 编译器的命令行工具,它将 C 和 C++ 源代码编译为通用对象文件格式(COFF) 对象文件(.obj接下来,链接器link.exe)会将这些对象文件合并成一个可执行文件(.exe)或动态链接库(.dll编译cl.exe将源代码编译为对象文件(.obj文件)。链接link.exe将这些对象文件与库文件一起链接,生成最终的可执行文件或动态链接库。通过本篇指南,你学习了如何使用 cl.exe编译 C/C++ 程序,并通过 link.exe。

u013454780的博客 3157

VCAST创建单元测试工程

选择工作目录,后面创建胡UT工程将会生成到这个目录。

qq_42851449的博客 2482

C/C++程序编译步骤以及如何生成可执行文件

一、开篇         许久不碰关于这方面的知识了,前几天同学开课提及到该部分,正好作为回顾吧。         C/C++语言很多人都比较熟悉,这基本上是每位大学生必学的一门编程语言,通常还都是作为程序设计入门语言学的,并且课程大多安排在大一(反正我是混过来的)。刚上大学,学生们还都很乖,学习也比较认真、用心。所以,C/C++语言掌握地也都不错(说的是你么),不用说编译程序,就是写个上几百

. 2万+

RandLA-Net Tensorrt 推理 c++

RandLA-Net 实现 Tensorrt 部署.

J。 1109

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

Linux 系统下 C/C++ 程序编译

一文理解Linux系统下,C/C++程序编译的过程!

qq_46457076的博客 7137

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

C程序编译、链接与项目构建

本篇博客对C/C++程序的编译环境、库创建链接、Make和CMake工具的使用进行介绍,以便加深理解和记忆

路遥知马力,日久见人心 1407

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

C/C++程序编译过程详解

C/C++程序编译过程详解 C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接。编译就是把文本形式源代码翻译为机器语言形式的目标文件的过程。链接是把目标文件、操作系统的启动代码和用到的库文件进行组织,形成最终生成可执行代码的过程。过程图解如下: 从图上可以看到,整个代码的编译过程分为编译和链接两个过程,编译对

u012662731的博客 6万+

【C/C++】编译与链接过程详解

如果程序中引用了一个未定义的符号(如函数或变量),链接器会报出“未定义符号”的错误。例如,调用了某个函数,但该函数的实现没有包含在目标文件或库文件中。检查是否忘记包含某个源文件或库文件。确保函数或变量已经正确定义。

卜及中的博客 1270

C 怎么读取Cpp文件_C/C++编程笔记:C/C++ 的编译和链接

C/C++文件C/C++程序文件包括 .h .c .hpp .cpp,其中源文件(.c .cpp)是基本的编译单元,头文件(.h .hpp)不会被编译器编译。C/C++项目构建(build)过程,分为以下几个步骤 预处理 → 编译 → 链接。预编译预编译的过程可以理解为编译器(实际上是预处理器,这里统称为编译器就可以了)在正式编译之前处理C/C++文件中的预处理命令,即#开头的代码。常用的几个预处...

weixin_39728572的博客 1213

GCC编译器——利用GCC编译器编译C/C++程序

以下内容源于C语言中文网的学习与整理,非原创,如有侵权请告知删除。

主要记录嵌入式学习与开发过程。 5728
上一篇: 手把手教你实现buffer(四)——webrtc中的CopyOnWriteBuffer
下一篇: C/C++程序编译与链接(二) 静态库的概念及使用
mo4776
博客等级 码龄10年 370粉丝 113原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mo4776

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值