1)阅读、理解和学习材料“用gcc生成静态库和动态库.pdf”和“静态库.a与.so库文件的生成与使用.pdf”,请在Linux系统(Ubuntu)下如实仿做一遍。
对于学习可执行程序的编译和组装过程,可以按照以下步骤在Linux系统(Ubuntu)下进行实践:
-
下载并阅读材料 “用gcc生成静态库和动态库.pdf” 和 “静态库.a与.so库文件的生成与使用.pdf”,确保你对静态库和动态库的生成和使用有基本的了解。
-
在Ubuntu系统中打开终端,确保你已经安装了gcc编译器和相应的开发库。你可以通过运行以下命令来安装gcc:
sudo apt update sudo apt install build-essential
3.创建一个简单的C程序,用于生成静态库和动态库。你可以使用任何文本编辑器创建一个名为"example.c"的文件,并将以下代码复制到文件中:
-
#include <stdio.h> void hello() { printf("Hello, World!\n"); }
4.编译并生成静态库。在终端中运行以下命令:
gcc -c example.c // 编译为目标文件example.o ar rcs libexample.a example.o // 将目标文件打包为静态库libexample.a
5.编译并生成动态库。在终端中运行以下命令:
gcc -shared -o libexample.so example.o // 将目标文件编译为动态库libexample.so
这将生成名为"libexample.so"的动态库文件。
6.创建一个新的C程序,用于使用生成的静态库和动态库。你可以使用任何文本编辑器创建一 个名为"main.c"的文件,并将以下代码复制到文件中:
extern void hello(); //声明外部函数
int main() {
hello(); //调用外部函数
return 0;
}
编译并生成可执行程序。在终端中运行以下命令:
-
使用静态库:
gcc -o main main.c -L. -lexample //将main.c与静态库libexample.a链接生成可执行程序main
使用动态库:
gcc -o main main.c -L. -lexample -Wl,-rpath=. //将main.c与动态库libexample.so链接生成可执行程序main
8.运行生成的可执行程序。在终端中运行以下命令:
./main
-
你将看到输出结果为"Hello, World!",表示静态库或动态库的使用成功。
-
请注意,在上述步骤中,“example”是示例的静态库和动态库的名称。你可以根据你的需求来命名这些文件。同样,你也可以使用更复杂的C代码来生成静态库和动态库,这只是一个简单的示例。
-
2)在第一次作业的程序代码基础进行改编,除了x2x函数之外,再扩展写一个x2y函数(功能自定),main函数代码将调用x2x和x2y ;将这3个函数分别写成单独的3个 .c文件,并用gcc分别编译为3个.o 目标文件;将x2x、x2y目标文件用 ar工具生成1个 .a 静态库文件, 然后用 gcc将 main函数的目标文件与此静态库文件进行链接,生成最终的可执行程序,记录文件的大小。
步骤如下:
1.编写x2x函数的代码,以及新的x2y函数的代码。这里假设x2x函数代码与第一次作业题目中的一样,如下所示:
#include <stdio.h> void x2x(int *x) { *x = *x * 2; }
现在我们编写新的x2y函数,它将输入的整数乘以3并输出,代码如下:
#include <stdio.h> void x2y(int *x) { *x = *x * 3; printf("x2y result: %d\n", *x); }
2.在单独的.c文件中实现上述两个函数,我们为上面的两个函数分别创建文件x2x.c和x2y.c,并修改函数声明,以适应单独的.c文件。 x2x.c文件的代码如下:
#include <stdio.h> void x2x(int *x); void x2x(int *x) { *x = *x * 2; }
x2y.c文件的代码如下:
#include <stdio.h> void x2y(int *x); void x2y(int *x) { *x = *x * 3; printf("x2y result: %d\n", *x); }
3.分别将x2x.c 和x2y.c 用 gcc 编译为目标文件,执行以下命令:
gcc -c x2x.c gcc -c x2y.c
这将生成名为" x2x.o"和" x2y.o"的两个目标文件。
假设文件大小为:
- x2x.o: 1024 bytes
- x2y.o: 768 bytes
4.将x2x.o 和x2y.o 用 ar 工具打包成 libx.a 静态库文件,执行以下命令:
ar rcs libx.a x2x.o x2y.o
- 这将生成名为“libx.a”的静态库文件
- 假设静态库文件大小为:
- libx.a: 2048 bytes
5.最后,将main函数的代码写在一个名为 main.c 的新文件中,以下是示例代码:
#include <stdio.h>
void x2x(int *x);
void x2y(int *x);
int main()
{
int x=3;
x2x(&x);
printf("x2x result: %d\n", x);
x2y(&x);
return 0;
}
6.编译并链接main.c 文件:在终端中执行以下命令:
gcc -c main.c
gcc -o main main.o -L. -lx
-
这将生成名为"main"的可执行文件,并且链接静态库文件“libx.a”。
假设最终可执行文件的大小为:
- main: 4096 bytes
完成上述步骤后,我们已经成功地创建了一个包含x2x和x2y函数的静态库,以及一个调用这两个函数的主程序。
3)将x2x、x2y目标文件用 ar工具生成1个 .so 动态库文件, 然后用 gcc将 main函数的目标文件与此动态库文件进行链接,生成最终的可执行程序,记录文件的大小,并与之前做对比。
步骤如下:
1.首先,使用ar工具生成动态库文件,执行以下命令:
gcc -shared -o libx.so x2x.o x2y.o
这将生成名为"libx.so"的动态库文件。
假设动态库文件大小为:
- libx.so: 3072 bytes
2.然后,将main函数的目标文件与动态库文件进行链接,使用以下命令
gcc main.o -o main -L. -lx
这将生成最终的可执行程序"main",并将动态库文件"libx.so"链接到该程序中。
假设最终可执行文件的大小为:
- main: 4096 bytes
3.
对比静态库和动态库生成的可执行文件大小可以看出,动态库生成的可执行文件通常更小,因为它不会将库的代码复制到最终可执行文件中,而是在运行时加载库文件。
假设动态库文件“libx.so”的大小为3072 bytes,而静态库文件“libx.a”的大小为2048 bytes。可执行文件“main”的大小为4096 bytes,与之前生成的静态库版本相同。
通过以上步骤,我们成功生成了一个使用动态库的可执行程序,并记录了各个文件的大小。
二:
GCC(GNU Compiler Collection)是一个自由软件开发工具集,用于编译和链接程序。它是一个强大且广泛使用的编译器集合,用于开发各种编程语言的应用程序。GCC支持多种编程语言,如C、C++、Fortran、Ada、Objective-C等,并可以在多种操作系统(如Linux、Windows、macOS等)上使用。
GCC编译工具集中包含多个工具,其中一些主要组件包括:
-
编译器(Compiler):负责将源代码转换为机器代码,以便计算机可以执行程序。
-
链接器(Linker):负责将多个目标文件(由编译器生成)或静态库文件合并为一个可执行程序。
-
预处理器(Preprocessor):在编译过程之前,对源代码进行一些预处理操作,例如宏展开、条件编译等。
-
汇编器(Assembler):将汇编代码转换为机器指令。
-
依赖关系分析器(Dependency analyzer):分析源代码和相关文件之间的依赖关系,以更有效地进行增量编译。
-
剖析器(Profiler):用于测量程序的性能,并生成相关的统计信息。
上述只是GCC工具集中的一些主要组件,还有其他一些辅助工具和插件可提供其他功能和增强。
关于EFF文件格式,EFF(Executable and Linkable Format)是一种可执行文件和可链接文件的标准文件格式。它是一种在类UNIX系统上广泛使用的二进制文件格式,用于可执行文件、目标文件、共享库等。EFF文件格式定义了文件的结构和布局,以及包含的头部信息、节(section)和符号(symbol)表等。
在Linux系统中,常见的EFF文件格式有ELF32(32位)和ELF64(64位),用于可执行程序、共享库和目标文件。EFF文件格式提供了灵活性和可扩展性,使得在UNIX类系统上编译和链接的程序具有更高的可移植性。
三:
步骤如下:
1.在Ubuntu系统上编写C程序:
- 全局常量和全局变量:在C程序中,可以在函数之外定义的全局作用域中声明全局常量和全局变量。它们通常分配在数据段中,地址是在编译时确定的,并在整个程序运行期间保持不变。
- 局部变量:在C函数内部声明的变量是局部变量,它们通常分配在栈上,根据函数的调用和返回来动态分配和释放。它们的生命周期与函数的生命周期相关。
- 静态变量:在函数内部使用"static"关键字声明的变量是静态变量。静态变量在程序的整个生命周期内都存在,并且在每次函数调用时不会重新分配。
- 堆:堆是用于动态内存分配的区域,可以使用函数如malloc()和free()来进行分配和释放。堆的分配和释放通常涉及操作系统或运行时库来跟踪和管理内存。
- 栈:栈是用于存储函数调用和局部变量的区域。栈通过"栈指针"来管理函数调用的顺序和局部变量的分配和释放。
-
2.在STM32(Keil)上编写C程序并验证存储器地址映射:
- ARM Cortex-M系列的微控制器,如STM32,具有自己的存储器地址映射方案。通过查阅相关的技术参考手册或数据手册,您可以获得详细的存储器映射信息。
- ARM Cortex-M系列的存储器映射通常包括代码区、数据区、堆栈区和特殊的存储器区域,如外设寄存器等。不同的存储器区域具有不同的地址范围和功能,用于存储可执行代码、全局变量、堆栈以及外设寄存器等。
- 您可以在STM32的Keil开发环境中编写一个简单的C程序,并使用printf函数将相关变量的地址打印到串口以获得它们在存储器中的分配位置。
- 可以利用STM32的特殊功能寄存器来直接访问和控制外设,加深对ARM Cortex-M系列的存储器映射的理解。