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

server/2024/11/26 3:07:04/

目录

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" 1void say_hello();
# 2 "main.c" 2int 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.ppmy.cn/server/144966.html

相关文章

大语言模型---RewardBench 介绍;RewardBench 的主要功能;适用场景

文章目录 1. RewardBench 介绍2. RewardBench 的主要功能3. 适用场景 1. RewardBench 介绍 RewardBench: Evaluating Reward Models是一个专门用于评估 Reward Models&#xff08;奖励模型&#xff09; 的公开平台&#xff0c;旨在衡量模型在多种任务上的性能&#xff0c;包括…

Rust 力扣 - 2266. 统计打字方案数

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 这题可以先求按了多少次相同连续的按钮&#xff0c;所有的连续相同按钮表示的方案数的乘积就是本题答案 我们的关键问题就转换成了按n个连续相同按钮表示的方案数 设f(i)表示按i个连续相同按钮表示的方案数 如…

P8692 [蓝桥杯 2019 国 C] 数正方形:结论,组合数学

题目描述 在一个 NNNN 的点阵上&#xff0c;取其中 44 个点恰好组成一个正方形的 44 个顶点&#xff0c;一共有多少种不同的取法&#xff1f; 由于结果可能非常大&#xff0c;你只需要输出模 10971097 的余数。 如上图所示的正方形都是合法的。 输入格式 输入包含一个整数 …

第十章 JavaScript的应用课后练习

第一题 <!DOCTYPE html> <html><head><meta charset"utf-8"><title>实时走动的数字时钟</title><style type"text/css">body{margin: 0;padding: 0;height: 100vh;text-decoration: none;text-align: center;}.…

C#里怎么样使用正则表达式?

C#里怎么样使用正则表达式? 正则表达式是由普通字符(如英文字母)以及特殊字符(也称为元字符)组成的一种文字模式 这种文字模式可用于检查字符串的值是否满足一定的规则,例如: 验证输入的邮箱是否合法 输入的身份证号码是否合法 输入的用户名是否满足条件等 也可以…

go 接口类型断言

类型断言是 Go 语言中的一种机制&#xff0c;用于将接口类型的值转换为具体类型。类型断言可以帮助我们从接口中提取具体的值&#xff0c;从而访问具体类型的方法和字段。 基本语法 类型断言的基本语法如下&#xff1a; value, ok : interfaceValue.(ConcreteType) interface…

RocketMQ: Broker 使用指南

Broker 配置参数 获取 Broker 的默认配置 $ sh mqbroker -m Broker 启劢时&#xff0c;如何加载配置 ### 第一步生成 Broker 默认配置模版 sh mqbroker -m > broker.p ### 第二步修改配置文件, broker.p ### 第三步加载修改过的配置文件 nohup sh mqbroker -c broker.pBrok…

如何在React中服务器操作提交表单后(不)重置表单?

在 React 中使用服务器操作提交表单时&#xff0c;你可能会遇到这样一个问题&#xff1a;如何在服务器操作执行后&#xff08;不&#xff09;重置表单。这取决于你在 React 之上使用的框架&#xff0c;表单可能会自动重置&#xff0c;也可能需要你手动重置。 在 React 中&…