Linux应用编程(C语言编译过程)

news2024/11/25 14:26:07

目录

1. 举例

2.预处理

2.1 预处理命令

2.2 .i文件内容解读

3.编译

4.汇编

5.链接

5.1 链接方式

5.1.1 静态链接

5.1.2 动态链接

5.1.3 混合链接


1. 举例

Linux的C语言开发,一般选择GCC工具链进行编译,通过下面的例子来演示GCC如何使用:

main.c

#include "hello.h"

int main()
{
    say_hello();
    return 0;
}

hello.h

#ifndef __HELLO_H__
#define __HELLO_H__

void say_hello();

#endif

hello.c

#include "hello.h"
#include <stdio.h>

void say_hello()
{
    printf("Hello world!\n");
}

采用如下命令编译可执行文件并执行:

gcc main.c hello.c -o main  // 编译
./main // 运行程序
  • -ooutput的缩写,表示输出,用于指定输出文件名。

2.预处理

2.1 预处理命令

       在C语言编译过程中,预处理是其中的第一个阶段,它的主要目的是处理源代码文件中的预处理指令,将它们转换成编译器可以识别的形式。预处理主要包含宏替换、文件包含、条件编译、注释移除等几种任务。预处理的输出通常是经过预处理后的源代码文件,它会被保存成一个临时文件,并作为编译器的输入。预处理器处理后的文件通常会比原始源文件大,因为它会展开宏和包含其他文件的内容。

命令格式如下:

gcc -E main.c -o main.i
  • -EExpand(展开)的缩写,该参数指定gcc执行预处理操作。
  • .iintermediate(中间的)的缩写,预处理后的源文件通常以.i作为后缀。

得到的main.i就是预处理之后的文件。我们可以查看内容:

# 0 "main.c"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 0 "<command-line>" 2
# 1 "main.c"
# 1 "hello.h" 1



void say_hello();
# 2 "main.c" 2

int main()
{
    say_hello();
    return 0;
}

2.2 .i文件内容解读

处理后的.i文件包含了经过C预处理器处理的源代码及行控制指令等内容。

.i文件中以#开头的是预处理器插入的行控制指令,用于标示从下一行起的代码来源,格式大致为

# 行号 "文件名" 标志

行号和文件名表示从下一行开始的源代码来源于哪个文件的哪一行。

标志可以是数字1,2,3,4,每个数字的含义如下:

  • 1: 表示接下来的内容开始于一个新的文件。
  • 2: 表示控制权从被包含的文件返回。这用于当预处理器完成一个包含文件的读取,回到包含它的文件继续处理时。
  • 3: 指示接下来的内容来自系统头文件。
  • 4: 表明接下来的内容应被视为被extern "C"包围,这主要用于C++中,以指示C链接约定。extern C是C++中的关键字组合,我们不必关注。

注:行号为0通常是预处理器的一种特殊标记用法,并不指向源代码中的实际行号。它可能用于初始化或特殊标记,比如标识文件的开始,而不直接对应于源代码中的行。

3.编译

       编译阶段,编译器会将经过预处理的源代码文件转换成汇编代码。在这个阶段,编译器会将源代码翻译成机器能够理解的中间代码,包括词法分析、语法分析、语义分析和优化等过程。编译器会检查代码的语法和语义,生成对应的汇编代码。编译阶段是整个编译过程中最复杂和耗时的阶段之一,它对源代码进行了深入的分析和转换,确保了程序的正确性和性能。

格式如下:

gcc -S main.i -o main.s
  • -SSource(源代码)的缩写,该参数指定gcc将预处理后的源码编译为汇编语言。
  • .sAssembly Source(汇编源码)的缩写,通常编译后的汇编文件以.s作为后缀。

4.汇编

       汇编阶段是C语言编译过程中的重要阶段,它将编译器生成的中间代码或汇编代码转换成目标机器的机器语言代码,也就是目标代码。这个阶段由汇编器(Assembler)完成,其主要任务是将汇编指令翻译成目标机器的二进制形式。主要包含以下几个任务:符号解析、指令翻译、地址关联、重定位、代码优化。最终,汇编器会将翻译和处理后的目标代码输出到目标文件中,用于后续的链接和生成可执行程序或共享库文件。

格式如下:

gcc -c main.s -o main.o
  •  -c可以被理解为Compile or Assemble(编译或汇编),该参数可以指定gcc将汇编代码翻译为机器码,但不做链接。此外,该参数也可以用于将.c文件直接处理为机器码,同样不做链接。
  • -oObject的缩写,通常汇编得到的机器码文件以.o为后缀。

5.链接

        链接阶段,由链接器完成。链接器将各个目标文件以及可能用到的库文件进行链接,生成最终的可执行程序。在这个阶段,链接器会解析目标文件中的符号引用,并将它们与符号定义进行匹配,以解决符号的地址关联问题。链接器还会处理全局变量的定义和声明,解决重定位问题,最终生成可执行文件或共享库文件。

5.1 链接方式

        我们在say_hello()函数中调用了printf()函数,这个函数是在stdio.h中声明的,后者来源于glibc库,printf()的实现在glibc的二进制组件中,通常是在共享库(如libc.so)或静态库(如libc.a)文件中。因此,我们除了要链接main.o、hello.o,还需要和glibc库的文件链接。通常,C语言的链接共有三种方式:静态链接、动态链接和混合链接。三者的区别就在于链接器在链接过程中对程序中库函数调用的解析。

5.1.1 静态链接

        将所有目标文件和所需的库在编译时一并打包进最终的可执行文件。库的代码被复制到最终的可执行文件中,使得可执行文件变得自包含,不需要在运行时查找或加载外部库。

格式如下:

gcc -static main.o hello.o -o main

-static该参数指示编译器进行静态链接,而不是默认的动态链接。使用这个参数,GCC会尝试将所有用到的库函数直接链接到最终生成的可执行文件中,包括C标准库(libc)、数学库(libm)和其他任何通过代码引用的外部库。

5.1.2 动态链接

        库在运行时被加载,可执行文件包含了需要加载的库的路径和符号信息。动态链接的可执行文件比静态链接的小,因为它们共享系统级的库代码。与静态链接不同,库代码不包含在可执行文件中。

方式一

gcc main.o hello.o -o main

没有添加-static关键字,gcc默认执行动态链接,即glibc库文件没有包含到可执行文件中。

方式二

执行下面的指令将hello.o编译为动态链接库libhello.so。

gcc -fPIC -shared -o libhello.so hello.o
  • -fPIC这个选项告诉编译器为“位置无关代码(Position Independent Code)”生成输出。在创建共享库时使用这个选项是非常重要的,因为它允许共享库被加载到内存中的任何位置,而不影响其执行。这是因为位置无关代码使用相对地址而非绝对地址进行数据访问和函数调用,使得库在被不同程序加载时能够灵活地映射到不同的地址空间。
  • -shared这个选项指示GCC生成一个共享库而不是一个可执行文件。共享库可以被多个程序同时使用,节省了内存和磁盘空间。
  • -o libhello.so这部分指定了输出文件的名称。-o选项后面跟着的是输出文件的名字,这里命名为libhello.so。按照惯例,Linux下的共享库名称以lib开头,扩展名为.so(表示共享对象)。
  • hello.o这是命令的输入文件,即之前编译生成的目标文件。在这个例子中,GCC会将hello.o中的代码和数据打包进最终的共享库libhello.so中。

上述命令的作用是:使用GCC,采用位置无关代码的方式,从hello.o目标文件创建一个名为libhello.so的动态共享库文件。

使用动态链接库编译新的可执行文件:

gcc main.o -L ./ -lhello -o main_d
  • -L ./指定了库文件搜索路径。-L选项告诉链接器在哪些目录下查找库文件,./表示当前目录。这意味着在链接过程中,链接器将会在当前目录下搜索指定的库文件。
  • -lhello指定了要链接的库。-l选项后面跟库的名称,这里是hello。根据约定,链接器会搜索名为libhello.so(动态库)或libhello.a(静态库)的文件来链接。链接器会根据-L选项指定的路径列表查找这个库。

这时如果我们直接执行main_d文件,会收到以下报错:

./main_d: error while loading shared libraries: libhello.so: cannot open shared object file: No such file or directory

这句报错的意思时main_d在执行过程中,没有找到动态链接库文件libhello.so文件,链接失败无法执行。Linux的默认动态链接库文件夹是/lib 和/usr/lib,而我们的libhello.so不在其中,所以我们需要在执行的时候指明额外的动态链接库文件夹。

解决方式: libhello.so放入/lib 和/usr/lib中(不推荐)

添加临时路径,格式如下:

LD_LIBRARY_PATH=/home/hello/helloworld ./main_d

5.1.3 混合链接

某些库静态链接,而其他库动态链接。这种方式结合了静态链接和动态链接的优点。

执行下面的指令可以将hello.o编译为静态链接库libhello.a

ar crv libhello.a hello.o
  • ar归档命令,用于处理静态库文件。
  • crvar命令的选项,由三个字符组成,每个字符代表一个选项:
  • c创建归档文件。如果指定的归档文件不存在,ar会创建它。
  • r替换归档文件中现有的文件或者向归档文件中添加新文件。如果hello.o已经在libhello.a中,它会被新版本替换;如果不存在,则会被添加。
  • v详细模式(verbose mode),在处理文件时显示详细信息。使用这个选项,ar会列出它正在执行的操作,包括哪些文件被添加或替换。
  • libhello.a要创建或更新的静态库文件的名称。按照惯例,Linux下的静态库文件名以lib开头,并以.a作为文件扩展名。
  • hello.o输入文件,即要添加到静态库libhello.a中的目标文件。此处只有一个目标文件hello.o,但ar命令支持同时指定多个文件。

利用静态库文件生成可执行的main文件:

gcc main.o -L ./ -lhello -o main
  • -L ./表示额外的库文件位置为当前目录;
  • -lhello表示链接libhello.a文件。注意这里要去掉开头的lib前缀和结尾的.a后缀。

编译完成后的main文件同样可以执行,并且不依赖于静态库libhello.a。

注意:如果相同目录下同时存在hello的静态库和动态库文件,链接时会默认选择动态链接。

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

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

相关文章

实时数据开发|简单理解Flink流计算中解决乱序的机制--水位线

今天继续学习Flink的关键机制–水位线&#xff0c;虽然看文字有种浮于表面、难以理解的感觉&#xff0c;但是我觉得等开发中使用到的时候就会融会贯通了。 定义 Fink 相比其他流计算技术的一个重要特性是支持基于事件时间(event time)的窗口操作。但是事件时间来自于源头系统…

Edify 3D: Scalable High-Quality 3D Asset Generation 论文解读

目录 一、概述 二、相关工作 1、三维资产生成 2、多视图下的三维重建 3、纹理和材质生成 三、Edify 3D 1、文本生成多视角图像的扩散模型 2、文本和多视角图像生成法线图像的ControlNet 3、重建与渲染模型 4、多视角高分辨率RGB图像生成 四、训练 1、训练过程 2、…

2025-2026财年美国CISA国际战略规划(下)

文章目录 前言四、加强综合网络防御&#xff08;一&#xff09;与合作伙伴共同实施网络防御&#xff0c;降低集体风险推动措施有效性衡量 &#xff08;二&#xff09;大规模推动标准和安全&#xff0c;以提高网络安全推动措施有效性衡量 &#xff08;三&#xff09;提高主要合作…

uniapp实现开发遇到过的问题(持续更新中....)

1. 在ios模拟器上会出现底部留白的情况 解决方案&#xff1a; 在manifest.json文件&#xff0c;找到开源码视图配置&#xff0c;添加如下&#xff1a; "app-plus" : {"safearea":{"bottom":{"offset" : "none" // 底部安…

文小言1:

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

Oracle 23ai 对应windows版本安装配置PLSQL导入pde文件navicat连接Oracle

因为有一个pde文件需要查看里面的数据&#xff0c;所以这次需要配置本地oracle数据库&#xff0c;并且导入数据&#xff0c;因为还有navicat&#xff0c;所以就想用navicat去连接查看。 1、找到官网。 Get Started with Oracle Database 23ai | Oracle 2、下载windows版本。…

【热门主题】000062 云原生后端:开启高效开发新时代

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 【热…

Python 版本的 2024详细代码

2048游戏的Python实现 概述&#xff1a; 2048是一款流行的单人益智游戏&#xff0c;玩家通过滑动数字瓷砖来合并相同的数字&#xff0c;目标是合成2048这个数字。本文将介绍如何使用Python和Pygame库实现2048游戏的基本功能&#xff0c;包括游戏逻辑、界面绘制和用户交互。 主…

spf算法、三类LSA、区间防环路机制/规则、虚连接

1.构建spf树&#xff1a; 路由器将自己作为最短路经树的树根根据Router-LSA和Network-LSA中的拓扑信息,依次将Cost值最小的路由器添加到SPF树中。路由器以Router ID或者DR标识。广播网络中DR和其所连接路由器的Cost值为0。SPF树中只有单向的最短路径,保证了OSPF区域内路由计管不…

(二)手势识别——动作模型训练【代码+数据集+python环境(免安装)+GUI系统】

&#xff08;二&#xff09;手势识别——动作模型训练【代码数据集python环境&#xff08;免安装&#xff09;GUI系统】 背景意义 随着互联网的普及和机器学习技术的进一步发展&#xff0c;手势识别技术开始使用深度学习等方法进行手势识别&#xff0c;如Convolutional Neural…

React的基本知识:事件监听器、Props和State的区分、改变state的方法、使用回调函数改变state、使用三元运算符改变state

这篇教学文章涵盖了大量的React基本知识。 包括&#xff1a; 事件监听器Props和State的区分改变state的方法使用回调函数改变state使用三元运算符改变state处理state中的数组处理state中的object条件渲染 &&条件渲染 三元运算符React中的forms 1. Event Listeners 在…

JavaScript练习——文本与图形

要求实现下面这个效果&#xff1a; 观察图片&#xff0c;我们的需求如下&#xff1a; 准备画布和上下文&#xff1a;在开始绘制之前&#xff0c;需要有一个HTML5 <canvas> 元素&#xff0c;并且获取其绘图上下文&#xff08;context&#xff09;&#xff0c;这是进行绘图…

【线程】线程安全问题及解决措施

【线程】线程安全问题及解决措施 前言一、由“随机调度”引起的线程安全问题1.1现象1.2 原因1.3 解决办法1.4 不当加锁造成的死锁问题 二、由“系统优化”引起的线程安全问题2.1 内存可见性问题 / 指令重排序问题2.2 解决方案 前言 何为线程安全&#xff0c;即某段代码无论在单…

[开源]3K+ star!微软Office的平替工具,跨平台,超赞!

大家好&#xff0c;我是JavaCodexPro&#xff01; 数字化的当下&#xff0c;高效的办公工具是提升工作效率的关键&#xff0c;然而大家想到的一定是 Microsoft Office 办公软件&#xff0c;然而价格也是相当具有贵的性价比。 今天JavaCodexPro给大家分享一款超棒的开源办公套…

【大数据分析机器学习】分布式机器学习

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈智能大数据分析 ⌋ ⌋ ⌋ 智能大数据分析是指利用先进的技术和算法对大规模数据进行深入分析和挖掘&#xff0c;以提取有价值的信息和洞察。它结合了大数据技术、人工智能&#xff08;AI&#xff09;、机器学习&#xff08;ML&a…

SOL链上的 Meme 生态发展:从文化到创新的融合#dapp开发#

一、引言 随着区块链技术的不断发展&#xff0c;Meme 文化在去中心化领域逐渐崭露头角。从 Dogecoin 到 Shiba Inu&#xff0c;再到更多细分的 Meme 项目&#xff0c;这类基于网络文化的加密货币因其幽默和社区驱动力吸引了广泛关注。作为近年来备受瞩目的区块链平台之一&…

一篇保姆式centos/ubuntu安装docker

前言&#xff1a; 本章节分别演示centos虚拟机&#xff0c;ubuntu虚拟机进行安装docker。 上一篇介绍&#xff1a;docker一键部署springboot项目 一&#xff1a;centos 1.卸载旧版本 yum remove docker docker-client docker-client-latest docker-common docker-latest doc…

Dubbo源码解析-Dubbo的线程模型(九)

一、Dubbo线程模型 首先明确一个基本概念&#xff1a;IO 线程和业务线程的区别 IO 线程&#xff1a;配置在netty 连接点的用于处理网络数据的线程&#xff0c;主要处理编解码等直接与网络数据 打交道的事件。 业务线程&#xff1a;用于处理具体业务逻辑的线程&#xff0c;可以…

前端全栈 === 快速入 门 Redis

目录 简介 通过 docker 的形式来跑&#xff1a; set、get 都挺简单&#xff1a; incr 是用于递增的&#xff1a; keys 来查询有哪些 key: redis insight GUI 工具。 list 类型 left push rpush lpop 和 rpop 自然是从左边和从右边删除数据。​编辑 如果想查看数据…

Python MySQL SQLServer操作

Python MySQL SQLServer操作 Python 可以通过 pymysql 连接 MySQL&#xff0c;通过 pymssql 连接 SQL Server。以下是基础操作和代码实战示例&#xff1a; 一、操作 MySQL&#xff1a;使用 pymysql python 操作数据库流程 1. 安装库 pip install pymysql2. 连接 MySQL 示例 …