文章目录

一.用gcc生成静态库和动态库

在 Linux 开发中,函数库是代码复用的重要方式,它能将公用函数封装起来供多个程序调用。函数库主要分为静态库和动态库,二者在编译链接机制与使用场景上差异显著。

1.静态库与动态库 的核心区别

在开始实操前,我们需要先明确两种库的本质差异,这是后续操作的基础:

  • 静态库:以.a为后缀,在程序编译阶段就会被完整连接到目标代码中。编译完成后,程序运行时不再依赖静态库本身,即使删除静态库,已生成的可执行文件也能正常运行。
  • 动态库:以.so为后缀,在程序编译阶段仅会记录依赖关系,不会嵌入代码;程序运行阶段才会被动态载入内存。因此,动态库必须始终存在于系统可查找的路径中,否则程序会启动失败。

2.准备工作:创建示例源文件

首先我们需要创建 3 个核心文件(hello.hhello.cmain.c),其中hello.c是库的核心实现,hello.h是头文件,main.c是测试程序。

2.1 创建工作目录

打开终端,执行以下命令创建专属工作目录(避免文件混乱),并进入该目录:

在这里插入图片描述

2.2编写三个核心文件

使用vimnanogedit等编辑器编写文件,这里以nano为例:

(1)头文件hello.h(声明库函数)
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name); // 声明公用函数hello
#endif // HELLO_H
(2)库实现文件hello.c(定义库函数)
#include <stdio.h>
// 实现hello函数,功能是在屏幕输出"Hello XXX!"
void hello(const char *name) {
    printf("Hello %s!\n", name);
}
(3)测试程序main.c(调用库函数)
#include "hello.h" // 包含库的头文件
int main() {
    hello("everyone"); // 调用库中的hello函数
    return 0;
}

在这里插入图片描述

3.编译生成目标文件(.o)

无论是静态库还是动态库,都需要先将库的源文件(hello.c)编译成目标文件(.o)—— 这是创建库的 “原材料”。

在终端执行以下命令,将hello.c编译为hello.o

gcc -c hello.c
  • 选项-c表示 “仅编译不链接”,只生成目标文件,不生成可执行文件。

执行ls命令验证结果,若目录中出现hello.o,则说明编译成功:

ls # 预期输出:hello.c  hello.h  hello.o  main.c

以下是实现结果图示:

4.创建与使用静态库(.a)

静态库的命名遵循 “lib+库名+.a” 的规则,例如库名为myhello,则静态库文件名为libmyhello.a。创建静态库需使用ar命令,使用时需通过 GCC 指定库路径与库名。

4.1 第 1 步:用.o 文件创建静态库

执行ar命令创建静态库libmyhello.a

ar -crv libmyhello.a hello.o
  • 选项说明:
    • -c:若静态库不存在,则创建它;
    • -r:将目标文件(hello.o)插入静态库,若库中已有同名文件则替换;
    • -v:显示详细操作过程(可选,便于排查问题)。

执行ls验证,目录中出现libmyhello.a即表示创建成功。

在这里插入图片描述

4.2 第 2 步:在程序中使用静态库

有 3 种常用方法可将静态库链接到测试程序main.c,最终生成可执行文件hello(以下以直接指定静态库文件路径为例):

gcc main.c libmyhello.a -o hello
  • 直接将静态库文件libmyhello.a作为输入文件,无需通过-L-l指定,适合库文件路径固定的场景。

4.3 验证静态库特性

运行生成的可执行文件hello,会输出Hello everyone!;接着删除静态库libmyhello.a,再次运行hello

./hello # 输出:Hello everyone!
rm libmyhello.a # 删除静态库
./hello # 仍输出:Hello everyone!

在这里插入图片描述

这验证了静态库的核心特性 —— 编译后可执行文件不依赖静态库本身。

5.创建与使用动态库(.so)

动态库的命名规则与静态库类似,为 “lib+库名+.so”,例如libmyhello.so。创建动态库需用 GCC 的-shared-fPIC选项,使用时需确保系统能找到动态库路径。

5.1 第 1 步:用.o 文件创建动态库

执行以下 GCC 命令创建动态库libmyhello.so

gcc -shared -fPIC -o libmyhello.so hello.o
  • 选项说明:
    • -shared:指定生成动态库(让连接器生成可被动态载入的符号表);
    • -fPIC:编译为 “位置独立代码”(Position-Independent Code),确保动态库能被多个进程共享内存,避免代码拷贝;
    • -o libmyhello.so:指定动态库文件名,-o不可省略。

执行ls验证,目录中出现libmyhello.so即创建成功。

在这里插入图片描述

5.2 第 2 步:在程序中使用动态库

编译链接命令与静态库完全相同,例如:

gcc -o hello main.c -L. -lmyhello

但此时直接运行hello会报错:

./hello # 报错:error while loading shared libraries: libmyhello.so: cannot open shared object file: No such file or directory

原因是:程序运行时,系统会默认在/usr/lib/lib等目录查找动态库,而当前动态库在test1目录,未被系统识别。

5.3 解决动态库路径问题

将动态库拷贝到系统默认路径

若有root权限,可将libmyhello.so拷贝到/usr/lib目录(系统默认库路径):

sudo mv libmyhello.so /usr/lib # 需输入管理员密码
./hello # 输出:Hello everyone!

在这里插入图片描述

5.4 验证动态库特性

若删除/usr/lib中的libmyhello.so(或删除环境变量中的路径),再次运行hello会报错,这说明动态库在程序运行时是必需的。

6.关键问题:静态库与动态库同名时的优先级

若当前目录中同时存在同名的静态库(libmyhello.a)和动态库(libmyhello.so),GCC 会优先使用动态库。

验证步骤:

1.恢复环境(删除之前的可执行文件和库,重新生成两种库):

rm -f hello hello.o /usr/lib/libmyhello.so
gcc -c hello.c
ar -cr libmyhello.a hello.o # 生成静态库
gcc -shared -fPIC -o libmyhello.so hello.o # 生成动态库

2.执行编译命令:

gcc -o hello main.c -L. -lmyhello

3.直接运行hello会报错(提示找不到libmyhello.so),说明 GCC 优先链接了动态库。

若需强制使用静态库,需在编译时直接指定静态库文件路径,例如:

gcc -o hello main.c libmyhello.a

在这里插入图片描述

二.静态库.a与.so库文件的生成与使用

在 Linux 开发中,库文件是代码复用和项目模块化的重要工具。以下将以实际案例带你一步步掌握静态库(.a)和共享库(.so)的生成流程与使用方法,操作步骤清晰,新手也能轻松上手。

1.前期准备:创建文件与目录

首先搭建实验环境,统一管理文件,避免混乱。

1.新建作业目录并进入,用于存放所有实验文件:

mkdir test2 && cd test2

2.用文本编辑器(vim/nano/gedit 均可)创建 4 个核心文件,文件功能与代码如下:

#include <stdio.h>//A1.c 
void print1(int arg)
{ printf("A1 print arg:%d\n",arg); }
#include <stdio.h>//A2.c
void print2(char *arg)
{ printf("A2 printf arg:%s\n", arg); }
#ifndef A_H//A.h
#define A_H
void print1(int);
void print2(char *);
#endif
#include <stdlib.h>//test.c
#include "A.h"
int main(){
print1(1);
print2("test");
exit(0);
}

在这里插入图片描述

2.静态库.a 的生成与使用

静态库在编译时会被完整拷贝到可执行文件中,运行时无需依赖外部库文件,适合对稳定性要求高的场景。

2.1 生成目标文件(.o)

将 A1.c 和 A2.c 编译为目标文件(二进制中间文件),执行命令:

gcc -c A1.c A2.c

执行后目录下会新增A1.oA2.o两个文件。

在这里插入图片描述

2.2 生成静态库.a

ar命令将目标文件打包为静态库,命名格式为libxxx.a(xxx 为自定义库名),命令如下:

ar crv libafile.a A1.o A2.o
  • c:创建新的静态库;r:将目标文件添加到库中(若库已存在则替换);v:显示操作过程。

在这里插入图片描述

2.3 使用静态库生成可执行程序

将 test.c 与静态库libafile.a链接,生成可执行文件test,命令:

gcc -o test test.c libafile.a

运行可执行文件,验证结果:

./test

在这里插入图片描述

3.共享库.so 的生成与使用

共享库在编译时仅记录引用关系,运行时才动态加载,可节省内存且便于库版本更新,但需确保运行时能找到库文件。

3.1 生成位置无关的目标文件(.o)

共享库要求目标文件为 “位置无关代码”,需添加-fpic参数,命令:

gcc -c -fpic A1.c A2.c

3.2 生成共享库.so

-shared参数将目标文件打包为共享库,命名格式为libxxx.so,命令:

gcc -shared *.o -o libsofile.so
  • *.o:匹配当前目录下所有.o 文件;-shared:指定生成共享库。

3.3 使用共享库生成可执行程序

链接共享库生成可执行文件,命令与静态库类似:

gcc -o test test.c libsofile.so

直接运行可能报错(系统默认只在/lib/usr/lib搜索共享库):

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

3.4 解决共享库 “找不到” 问题

拷贝共享库到系统默认路径

libsofile.so拷贝到/usr/lib(需管理员权限):

sudo cp libsofile.so /usr/lib

再次运行,成功输出结果:

./test
# 输出:A1 print arg:1  A2 printf arg:test

在这里插入图片描述

三.静态库与动态库实践:从基础函数扩展到库文件应用

在 Linux 开发中,库文件是代码复用和项目模块化的重要工具。库分为静态库(.a)和动态库(.so),两者在编译链接方式、文件大小和运行机制上差异显著。本文将通过一个完整案例,从代码编写到最终运行,详细演示静态库与动态库的制作流程,并对比两者的文件大小差异,帮助大家理解两种库的适用场景。

1.基础代码编写

首先我们需要创建 3 个核心文件:x2x.c(实现两数除法)、x2y.c(实现勾股定理计算)和main.c(主函数调用),构成一个简单的数学计算程序。

1.1 x2x.c:两数除法函数

// x2x.c:计算两个整数相除的浮点结果
float x2x(int a, int b) {
    // 防止除零错误,返回0.0
    if (b == 0) {
        return 0.0f;
    }
    // 强制类型转换为浮点型,避免整数除法
    return (float)a / b;
}

1.2 x2y.c:勾股定理计算函数

// x2y.c:计算两数平方和的平方根(勾股定理)
#include <math.h>  // 需调用sqrt函数

float x2y(int a, int b) {
    // 先计算平方和,再求平方根
    return sqrt((float)(a*a + b*b));
}

1.3 main.c:主函数调用

// main.c:主函数,调用x2x和x2y并输出结果
#include <stdio.h>
// 函数声明(告诉编译器函数存在)
float x2x(int a, int b);
float x2y(int a, int b);
int main() {
    // 定义两个整数变量(3和4是勾股数,方便验证x2y结果)
    int num1 = 3, num2 = 4;
    // 调用两个库函数
    float res_x2x = x2x(num1, num2);
    float res_x2y = x2y(num1, num2);  
    // 格式化输出结果(保留2位小数)
    printf("x2x(%d, %d) = %.2f\n", num1, num2, res_x2x);
    printf("x2y(%d, %d) = %.2f\n", num1, num2, res_x2y);
    return 0;
}

2.静态库(.a)制作与链接全流程

静态库的核心特点是编译时将库代码嵌入可执行程序,程序运行时无需依赖外部库文件。

2.1 编译目标文件(.o)

首先将.c文件编译为位置无关的目标文件(静态库对位置无关代码无强制要求,但动态库需要,这里统一操作):

gcc -c -fPIC x2x.c -o x2x.o
gcc -c -fPIC x2y.c -o x2y.o
gcc -c main.c -o main.o

执行后通过ls *.o可看到生成的 3 个目标文件:x2x.o、x2y.o、main.o。

2.2 制作静态库(.a)

使用ar工具将目标文件打包为静态库,命名需遵循libxxx.a格式(方便后续链接时调用):

ar rcs libmymath.a x2x.o x2y.o

执行后生成静态库libmymath.a,可通过file libmymath.a验证:输出libmymath.a: current ar archive表示静态库创建成功。

2.3 链接生成可执行程序

将main.o与静态库链接,生成可执行程序myapp_static:

gcc main.o -L. -lmymath -lm -o myapp_static

2.4 运行静态链接程序

直接执行程序,无需额外依赖:

./myapp_static

输出结果(符合预期):

x2x(3, 4) = 0.75
x2y(3, 4) = 5.00

实验结果应为下图所示:

在这里插入图片描述

3.动态库(.so)制作与链接全流程

动态库的核心特点是编译时仅记录库依赖,运行时才加载库代码,多个程序可共享同一动态库,节省磁盘和内存空间。

3.1 重新编译目标文件(强制 - fPIC)

动态库必须使用位置无关代码,因此需确保目标文件编译时添加-fPIC:

gcc -c -fPIC x2x.c -o x2x.o
gcc -c -fPIC x2y.c -o x2y.o
gcc -c main.c -o main.o

3.2 制作动态库(.so)

使用gcc的-shared参数生成动态库:

gcc -shared -o libmymath.so x2x.o x2y.o

3.3 链接生成可执行程序

与静态库链接命令类似,但生成的程序依赖动态库:

gcc main.o -L. -lmymath -lm -o myapp_dynamic

3.4 运行动态链接程序(解决库依赖)

动态链接程序运行时需找到动态库,否则会报错

# 将动态库复制到系统默认库目录(如/usr/local/lib)
sudo cp libmymath.so /usr/local/lib/
# 更新系统库缓存
sudo ldconfig
# 运行程序
./myapp_dynamic

实验结果如下图:

在这里插入图片描述

4.主要差异总结

  1. 动态库本身比静态库大很多
  2. 动态链接的可执行文件比静态链接的小
  3. 使用 - fPIC 编译的目标文件比普通目标文件稍大
  4. 静态链接会将库代码嵌入可执行文件,而动态链接在运行时才加载库

四.深入从源码到可执行文件的探索

GCC并非单一工具,而是由编译器、二进制工具集(Binutils)和 C 运行库等组成的完整编译工具链。本次实验通过手动执行 GCC 编译的四个核心阶段(预处理、编译、汇编、链接),深入理解每个阶段的作用,同时掌握 Binutils 工具集中关键工具的使用,并分析 ELF文件格式的结构,最终实现从 C 语言源码到可执行文件的全流程实践。

1.创建源文件

首先创建工作目录test0,并在该目录下编写hello.c源文件,代码如下:

#include <stdio.h>
// 简单的Hello World程序,用于演示GCC编译流程
int main(void)
{
    printf("Hello World! \n");
    return 0;
}

2.GCC 编译全流程实践

GCC 编译 C 语言程序分为预处理、编译、汇编、链接四个阶段,每个阶段可通过指定参数单独执行,以下为详细步骤与结果。

阶段 1:预处理(生成.i文件)

作用

  • 删除所有#define并展开宏定义;
  • 处理#include指令,将头文件(如stdio.h)内容插入到源文件中;
  • 删除注释(///* */);
  • 添加行号和文件标识,便于后续调试与错误定位;
  • 保留#pragma编译器指令。
gcc -E hello.c -o hello.i
  • -E:指定 GCC 仅执行预处理阶段,完成后停止;
  • -o hello.i:将预处理结果输出到hello.i文件(若不指定-o,结果会直接打印到终端)。

在这里插入图片描述

阶段 2:编译(生成.s汇编文件)

作用

对预处理后的hello.i文件进行词法分析、语法分析、语义分析,并进行代码优化,最终生成汇编语言代码。

gcc -S hello.i -o hello.s
  • -S:指定 GCC 仅执行编译阶段,生成汇编文件后停止;
  • -o hello.s:将汇编代码输出到hello.s文件。

在这里插入图片描述

阶段3:汇编(生成.o目标文件)

作用

调用 Binutils 中的汇编器as,将汇编代码(hello.s)翻译成处理器可识别的机器指令,生成 ELF 格式的可重定向目标文件(.o)。

使用 GCC 间接调用as

gcc -c hello.s -o hello.o

在这里插入图片描述

阶段4:链接(生成可执行文件)

作用

将目标文件(hello.o)与C 标准库(如libc.so 或其他依赖库链接,解决符号引用(如printf函数的实现),最终生成 ELF 格式的可执行文件。 动态链接

gcc hello.o -o hello_dynamic
  • 未指定-static时,GCC 优先链接动态库(如libc.so),生成的可执行文件体积较小。

在这里插入图片描述

3.Binutils 工具集核心工具实践

Binutils 是 GCC 背后的 “战友”,包含一组二进制处理工具,以下为关键工具的使用示例。

1.addr2line:地址定位源码

将程序地址转换为对应的源文件、行号和函数,用于调试时定位错误位置。

  1. 生成带调试信息的可执行文件(-g参数):

    gcc -g hello.c -o hello_debug
    
  2. objdump -D查看main函数的地址:

    objdump -D hello_debug | grep -A 10 "<main>"
    
  3. addr2line定位地址对应的源码:

    addr2line -e hello_debug 0000000000400526
    

在这里插入图片描述

2.objdump:反汇编 ELF 文件

对 ELF 文件进行反汇编,查看机器指令与汇编代码的对应关系,支持混合显示 C 源码(需-g调试信息)。

objdump -D hello_dynamic | grep -A 10 "<main>"##纯反汇编
objdump -S hello_debug | grep -A 10 "<main>"##混合显示源码与汇编

在这里插入图片描述

在这里插入图片描述

3.readelf:查看 ELF 文件结构

显示 ELF 文件的详细信息,包括段头表、符号表、文件头信息等,用于分析 ELF 格式。

readelf -S hello_dynamic

在这里插入图片描述

在这里插入图片描述

  1. GCC 编译流程:预处理(.c→.i)→编译(.i→.s)→汇编(.s→.o)→链接(.o→可执行文件),每个阶段的输出文件类型和作用明确,缺一不可。
  2. Binutils 工具集as(汇编)、ld(链接)、objdump(反汇编)、readelf(ELF 分析)、addr2line(地址定位)等工具是 GCC 的核心辅助,用于调试和分析二进制文件。
  3. ELF 文件格式:通过段(.text.data.bss等)组织数据和代码,不同段的权限和作用不同,决定了程序的执行方式。
  4. 动态链接与静态链接:动态链接体积小、节省内存(共享库),但依赖系统库;静态链接不依赖外部库,可移植性强,但体积大,需根据场景选择。

五.C 程序变量存储位置探究:Ubuntu 与 STM32 对比实验

在 C 语言中,变量根据其作用域和存储类型,会被分配到内存的不同区域(堆、栈、全局区等)。不同架构的系统(如 x86_64 的 Ubuntu 和 ARM Cortex-M3 的 STM32)由于存储器地址映射的差异,变量的存储位置会呈现不同特征。

1.实验程序设计

设计一个包含各类变量的 C 程序,通过打印变量地址来分析其存储区域:

#include <stdio.h>
#include <stdlib.h>
// 全局变量(.data或.bss段)
int g_var;                  // 未初始化全局变量(.bss)
int g_init_var = 10;        // 已初始化全局变量(.data)
// 全局常量(.rodata段)
const int g_const = 20;     // 全局常量(.rodata)
// 静态全局变量(.data或.bss段)
static int g_static_var;    // 未初始化静态全局变量(.bss)
static int g_static_init_var = 30;  // 已初始化静态全局变量(.data)
void test_func() {
    // 局部变量(栈)
    int l_var;              // 局部变量(栈)
    // 静态局部变量(.data或.bss段)
    static int l_static_var;        // 未初始化静态局部变量(.bss)
    static int l_static_init_var = 40;  // 已初始化静态局部变量(.data)
    
    printf("--- 函数内变量地址 ---\n");
    printf("局部变量 l_var:         %p\n", &l_var);
    printf("静态局部变量 l_static_var: %p\n", &l_static_var);
    printf("初始化静态局部变量 l_static_init_var: %p\n", &l_static_init_var);
}
int main() {
    // 局部变量(栈)
    int m_var;
    // 堆变量(堆)
    int* heap_var = (int*)malloc(sizeof(int));   
    printf("--- 全局变量地址 ---\n");
    printf("未初始化全局变量 g_var: %p\n", &g_var);
    printf("初始化全局变量 g_init_var: %p\n", &g_init_var);
    printf("全局常量 g_const:       %p\n", &g_const);
    printf("未初始化静态全局变量 g_static_var: %p\n", &g_static_var);
    printf("初始化静态全局变量 g_static_init_var: %p\n", &g_static_init_var);    
    printf("\n--- 主函数内变量地址 ---\n");
    printf("局部变量 m_var:         %p\n", &m_var);
    printf("堆变量 heap_var:        %p\n", heap_var);    
    test_func();   
    free(heap_var);
    return 0;
}

程序说明:

  • 包含 6 类变量:未初始化全局变量、已初始化全局变量、全局常量、静态全局变量、局部变量、静态局部变量、堆变量
  • 通过printf打印变量地址(STM32 中通过串口输出)
  • 堆变量通过malloc动态分配,验证堆区域地址

实验结果如图

在这里插入图片描述

  1. x86_64 采用虚拟地址映射,地址空间为 64 位(实际常用低 48 位)
  2. 存储区域地址从低到高排序:.rodata < .data < .bss < 堆 < 栈
  3. 堆和栈向相反方向增长(堆向上,栈向下)
  4. 静态变量(无论全局还是局部)均存储在.data.bss,与全局变量同区域

2.STM32(Cortex-M3)环境实验结果

  1. 硬件:STM32F103C8T6 最小系统板(20KB RAM,64KB Flash)
  2. 软件:Keil MDK 5.36,配置 USART1(115200 波特率)用于串口输出
  3. 程序修改:将printf重定向到串口(通过重写fputc函数)

串口重定向代码(必要部分):

#include "stdio.h"

int fputc(int ch, FILE *f) {
    HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, 0xFFFF);
    return ch;
}

实验结果(串口助手输出):

--- 全局变量地址 ---
未初始化全局变量 g_var: 0x20000008
初始化全局变量 g_init_var: 0x20000000
全局常量 g_const:       0x080003F0
未初始化静态全局变量 g_static_var: 0x2000000C
初始化静态全局变量 g_static_init_var: 0x20000004

--- 主函数内变量地址 ---
局部变量 m_var:         0x200004F8
堆变量 heap_var:        0x20000100

--- 函数内变量地址 ---
局部变量 l_var:         0x200004F4
静态局部变量 l_static_var: 0x20000010
初始化静态局部变量 l_static_init_var: 0x2000000C

实验结果如图:

在这里插入图片描述

  1. Cortex-M3 采用物理地址映射,地址空间固定(32 位)
  2. 存储区域物理划分明确:
    • Flash(程序区):0x08000000 起(存放代码、常量)
    • RAM(数据区):0x20000000 起(存放全局变量、堆、栈)
  3. RAM 内地址从低到高排序:.data < .bss < 堆 < 栈(与 Ubuntu 逻辑一致)
  4. 栈地址最高(STM32 默认栈顶地址在 RAM 末端,由启动文件定义)

3.Ubuntu 与 STM32 的存储模型对比分析

对比项 Ubuntu(x86_64) STM32(Cortex-M3)
地址类型 虚拟地址(MMU 映射) 物理地址(无 MMU,直接映射)
地址空间 64 位(实际使用 48 位) 32 位
Flash/ROM 用途 存储可执行文件(运行时加载到 RAM) 直接存储代码和常量(运行时不加载)
RAM 包含区域 .data、.bss、堆、栈 .data、.bss、堆、栈(与 x86 一致)
常量存储 虚拟地址空间的.rodata 段(物理可能在 RAM) 直接存储在 Flash(0x08000000 区)
堆 / 栈增长方向 堆向上,栈向下 堆向上,栈向下(逻辑一致)
地址范围特征 各区域地址跨度大(虚拟地址映射) 各区域地址连续(物理地址直接分配)

4.Cortex-M3/STM32F10x 存储器地址映射深入理解

Cortex-M3 内核采用统一地址映射(Harvard 架构但地址统一编址),STM32F10x 的存储器映射如下(关键区域):

地址范围 存储器类型 用途
0x00000000-0x07FFFFFF 代码区 可映射 Flash、SRAM 或外设(BOOT 引脚配置启动方式)
0x08000000-0x0807FFFF 主 Flash 存放程序代码、常量(STM32F103C8T6 为 64KB)
0x1FFFF000-0x1FFFF7FF 系统存储器 内置 Bootloader(ISP 下载用)
0x20000000-0x2000FFFF SRAM 存放全局变量、堆、栈(STM32F103C8T6 为 20KB)
0x40000000-0x5FFFFFFF 外设寄存器 片上外设(GPIO、USART 等)地址映射
0xE0000000-0xE00FFFFF 内核外设 NVIC、SysTick 等内核组件寄存器

结论:

  1. 变量存储共性
    • 无论是 x86_64 还是 ARM Cortex-M3,变量存储区域的逻辑划分一致(.rodata、.data、.bss、堆、栈)
    • 静态变量(全局 / 局部)均存储在.data/.bss,与全局变量同区域
    • 堆和栈均向相反方向增长(堆向上,栈向下)
  2. 架构差异核心
    • x86_64 依赖 MMU 实现虚拟地址映射,地址空间大且不直接对应物理存储器
    • Cortex-M3 无 MMU,采用物理地址直接映射,地址范围固定且与硬件严格对应
    • STM32 的常量存储在 Flash(掉电不丢失),而 Ubuntu 的常量在虚拟地址的.rodata 段(物理可能在 RAM)
  3. STM32 存储器映射关键
    • 地址空间严格分区,Flash(0x08000000)和 RAM(0x20000000)是程序运行的核心区域
    • 栈顶地址由启动文件定义(通常为 RAM 末端),堆由编译器分配在.bss 之后
    • 外设通过内存映射方式访问(如 USART 寄存器地址固定)

六.总结

本文围绕嵌入式开发中的库文件使用与程序编译运行机制展开深入实践。首先详细介绍了在 Linux 环境下,静态库(.a)和动态库(.so)的创建流程与使用方法,包括源文件准备、目标文件编译、库文件生成及链接使用,并对比了两者在编译链接机制、依赖关系及优先级上的差异。

接着,通过具体案例演示了从基础函数编写到静态库与动态库制作、链接的全流程,直观呈现了两种库在文件大小等方面的区别。

此外,深入剖析了 GCC 编译的四个核心阶段(预处理、编译、汇编、链接),并介绍了 Binutils 工具集的关键工具(如 addr2line、objdump、readelf)在二进制文件分析中的应用,以及 ELF 文件格式的结构特点。

最后,设计实验对比了 Ubuntu(x86_64 架构)与 STM32(Cortex-M3 架构)中 C 程序变量的存储位置,分析了不同架构下存储器地址映射的差异及变量存储的共性与特性,展现了从代码到可执行文件再到内存存储的完整底层逻辑。
ss,与全局变量同区域
- 堆和栈均向相反方向增长(堆向上,栈向下)
2. 架构差异核心
- x86_64 依赖 MMU 实现虚拟地址映射,地址空间大且不直接对应物理存储器
- Cortex-M3 无 MMU,采用物理地址直接映射,地址范围固定且与硬件严格对应
- STM32 的常量存储在 Flash(掉电不丢失),而 Ubuntu 的常量在虚拟地址的.rodata 段(物理可能在 RAM)
3. STM32 存储器映射关键
- 地址空间严格分区,Flash(0x08000000)和 RAM(0x20000000)是程序运行的核心区域
- 栈顶地址由启动文件定义(通常为 RAM 末端),堆由编译器分配在.bss 之后
- 外设通过内存映射方式访问(如 USART 寄存器地址固定)

六.总结

本文围绕嵌入式开发中的库文件使用与程序编译运行机制展开深入实践。首先详细介绍了在 Linux 环境下,静态库(.a)和动态库(.so)的创建流程与使用方法,包括源文件准备、目标文件编译、库文件生成及链接使用,并对比了两者在编译链接机制、依赖关系及优先级上的差异。

接着,通过具体案例演示了从基础函数编写到静态库与动态库制作、链接的全流程,直观呈现了两种库在文件大小等方面的区别。

此外,深入剖析了 GCC 编译的四个核心阶段(预处理、编译、汇编、链接),并介绍了 Binutils 工具集的关键工具(如 addr2line、objdump、readelf)在二进制文件分析中的应用,以及 ELF 文件格式的结构特点。

最后,设计实验对比了 Ubuntu(x86_64 架构)与 STM32(Cortex-M3 架构)中 C 程序变量的存储位置,分析了不同架构下存储器地址映射的差异及变量存储的共性与特性,展现了从代码到可执行文件再到内存存储的完整底层逻辑。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐