gcc背后的故事

一、学习并掌握可执行程序的编译、组装过程

    用gcc生成 .a静态库和 .so动态库

        准备举例用的源程序,并将函数库的源程序编译成.o 文件

(1) 1 步:编辑生成例子程序 hello.h hello.c main.c
先创建一个作业目录,保存本次练习的文件。
#mkdir test1
#cd test1
然后用 vim nano gedit 等文本编辑器编辑生成所需要的 3 个文件。
hello.c( 见程序 2) 是函数库的源程序,其中包含公用函数 hello ,该函数将在屏幕上输出 "Hello
XXX!" hello.h( 见程序 1) 为该函数库的头文件。 main.c( 见程序 3) 为测试库文件的主程序,
在主程序中调用了公用函数 hello
程序 1: hello.h
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
#endif //HELLO_H
程序 2: hello.c
#include <stdio.h>
void hello(const char *name)
{
printf("Hello %s!\n", name);
}
程序 3: main.c
#include "hello.h"
int main()
{
hello("everyone");
return 0;
}
(2) 2 步:将 hello.c 编译成 .o 文件。 无论静态库,还是动态库,都是由 .o 文件创建的。因此,我们必须将源程序 hello.c 通过 g
cc 先编译成 .o 文件。在系统提示符下键入以下命令得到 hello.o 文件。
# gcc -c hello.c
#
我们运行 ls 命令看看是否生存了 hello.o 文件。
# ls
hello.c hello.h hello.o main.c
#
ls 命令结果中,我们看到了 hello.o 文件,本步操作完成。
下面我们先来看看如何创建静态库,以及使用它。
(3) 3 步:由 .o 文件创建静态库。
静态库文件名的命名规范是以 lib 为前缀,紧接着跟静态库名,扩展名为 .a 。例如:我们将
创建的静态库名为 myhello ,则静态库文件名就是 libmyhello.a 。在创建和使用静态库时,
需要注意这点。创建静态库用 ar 命令。在系统提示符下键入以下命令将创建静态库文件
libmyhello.a
# ar -crv libmyhello.a hello.o
#
我们同样运行 ls 命令查看结果:
# ls
hello.c hello.h hello.o libmyhello.a main.c
#
ls 命令结果中有 libmyhello.a
(4) 4 步:在程序中使用静态库。
静态库制作完了,如何使用它内部的函数呢?只需要在使用到这些公用函数的源程序中包
含这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明静态库名, gcc 将会从
静态库中将公用函数连接到目标文件中。注意, gcc 会在静态库名前加上前缀 lib ,然后追
加扩展名 .a 得到的静态库文件名来查找静态库文件。
在程序 3:main.c 中,我们包含了静态库的头文件 hello.h ,然后在主程序 main 中直接调用
公用函数 hello 。下面先生成目标程序 hello ,然后运行 hello 程序看看结果如何。
方法一:
# gcc -o hello main.c -L. –lmyhello
自定义的库时, main.c 还可放在 -L. –lmyhello 之间,但是不能放在它俩之后,否则会提
myhello 没定义,但是是系统的库时,如 g++ -o main -L/usr/lib -lpthread main.cpp
就不出错。
方法二:
#gcc main.c libmyhello.a -o hello 方法三:
先生成 main.o
gcc -c main.c
再生成可执行文件:
gcc -o hello main.o libmyhello.a
动态库连接时也可以这样做。
# ./hello
Hello everyone!
#
我们删除静态库文件试试公用函数 hello 是否真的连接到目标文件 hello 中了。
# rm libmyhello.a
rm: remove regular file `libmyhello.a'? y
# ./hello
Hello everyone!
#
程序照常运行,静态库中的公用函数已经连接到目标文件中了。
我们继续看看如何在 Linux 中创建动态库。我们还是从 .o 文件开始。
(5) 5 步:由 .o 文件创建动态库文件。
动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀 lib ,但其
文件扩展名为 .so 。例如:我们将创建的动态库名为 myhello ,则动态库文件名就是 libmyh
ello.so 。用 gcc 来创建动态库。
在系统提示符下键入以下命令得到动态库文件 libmyhello.so
# gcc -shared -fPIC -o libmyhello.so hello.o -o 不可少)
#
我们照样使用 ls 命令看看动态库文件是否生成。
# ls
hello.c hello.h hello.o libmyhello.so main.c
#
(6) 6 步:在程序中使用动态库;
在程序中使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含
这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明动态库名进行编译。我
们先运行 gcc 命令生成目标文件,再运行它看看结果。
# gcc -o hello main.c -L. -lmyhello ( #gcc main.c libmyhello.so -o hello 不会出错(没有 libmyhello.so 的话,会出错),但是
接下来 ./hello 会提示出错,因为虽然连接时用的是当前目录的动态库,但是运行时,是到
/usr/lib 中找库文件的,将文件 libmyhello.so 复制到目录 /usr/lib 中就 OK )
# ./hello
./hello: error while loading shared libraries: libmyhello.so: cannot open shar
ed object file: No such file or directory
#
哦!出错了。快看看错误提示,原来是找不到动态库文件 libmyhello.so 。程序在运行时,
会在 /usr/lib /lib 等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提
示类似上述错误而终止程序运行。我们将文件 libmyhello.so 复制到目录 /usr/lib 中,再试
试。
# mv libmyhello.so /usr/lib
# ./hello
Hello everyone!
#
成功了。这也进一步说明了动态库在程序运行时是需要的。
我们回过头看看,发现使用静态库和使用动态库编译成目标程序使用的 gcc 命令完全一样,
那当静态库和动态库同名时, gcc 命令会使用哪个库文件呢 ?抱着对问题必究到底的心情,
来试试看。
先删除除 .c .h 外的所有文件,恢复成我们刚刚编辑完举例程序状态。
# rm -f hello hello.o /usr/lib/libmyhello.so
# ls
hello.c hello.h main.c
#
再来创建静态库文件 libmyhello.a 和动态库文件 libmyhello.so
# gcc -c hello.c
# ar -cr libmyhello.a hello.o (或 -cvr
# gcc -shared -fPIC -o libmyhello.so hello.o
# ls
hello.c hello.h hello.o libmyhello.a libmyhello.so main.c
#
通过上述最后一条 ls 命令,可以发现静态库文件 libmyhello.a 和动态库文件 libmyhello.s
o 都已经生成,并都在当前目录中。然后,我们运行 gcc 命令来使用函数库 myhello 生成目
标文件 hello ,并运行程序 hello
# gcc -o hello main.c -L. –lmyhello
(动态库和静态库同时存在时,优先使用动态库,当然,如果直接
#gcc main.c libmyhello.a -o hello 的话,就是指定为静态库了) # ./hello
./hello: error while loading shared libraries: libmyhello.so: cannot open shar
ed object file: No such file or directory
#
从程序 hello 运行的结果中很容易知道,当静态库和动态库同名时, gcc 命令将优先使用动
态库,默认去连 /usr/lib /lib 等目录中的动态库,将文件 libmyhello.so 复制到目录 /usr/lib
中即可。

以下为所有运行结果:

    在第一次作业的程序代码基础进行改编,除了x2x函数之外,再扩展写一个x2y函数(功能自定),main函数代码将调用x2x和x2y ;将这3个函数分别写成单独的3个 .c文件,并用gcc分别编译为3个.o 目标文件;将x2x、x2y目标文件用 ar工具生成1个 .a 静态库文件, 然后用 gcc将 main函数的目标文件与此静态库文件进行链接,生成最终的可执行程序,记录文件的大小。

扩写x2y函数

将3个 .c文件编译为 .o文件

将x2x、x2y目标文件用 ar工具生成1个 .a 静态库文件

然后用 gcc将 main函数的目标文件与此静态库文件进行链接,生成最终的可执行程序

先生成 main.o
gcc -c main.c
再生成可执行文件:
gcc -o sub main.o libmysub.a

再来看可执行文件sub的大小

可以看见sub的大小为8.4KB。

将x2x、x2y目标文件用 ar工具生成1个 .so 动态库文件, 然后用 gcc将 main函数的目标文件与此动态库文件进行链接,生成最终的可执行程序

先生成.so动态文件库,如图所示

再连接文件库,生成可执行程序

 二、Gcc不是一个人在战斗。请说明gcc编译工具集中各软件的用途,了解EFF文件格式。学习任务如下:阅读、理解和学习材料“Linux GCC常用命令.pdf”和“GCC编译器背后的故事.pdf”,如实仿做一遍

 GCC(GNU Compiler Collection)是一套开源的编译器工具集,它由多个组件构成,每个组件都有不同的用途。下面是GCC中几个常见组件的用途说明:

  1. GNU C编译器(GCC):GCC最初是为C语言编写的编译器,但后来它成为了支持其他编程语言的广泛编译器。它是GCC工具集的核心组件,可将C、C++、Objective-C和Objective-C++等高级语言源代码翻译成可执行的机器代码。

  2. GNU C++编译器(G++):G++是GCC的C++编译器,可将C++语言源代码编译成可执行的机器代码。它是在GCC的基础上针对C++语言进行了扩展和改进。

  3. GNU Fortran编译器(GFortran):GFortran是GCC的Fortran编译器,用于将Fortran语言源代码编译成可执行的机器代码。它支持多个Fortran标准,并具有丰富的优化选项。

  4. GNU Ada编译器(GNAT):GNAT是GCC的Ada编译器,用于将Ada语言源代码编译成可执行的机器代码。它支持多个Ada标准,并提供了广泛的编译选项。

  5. GNU Objective-C编译器(GCC-ObjC):GCC-ObjC是GCC的Objective-C编译器,用于将Objective-C语言源代码编译成可执行的机器代码。它是在GCC的基础上为Objective-C语言进行了扩展和改进。

除了以上几个组件外,GCC还包含其他工具和库,如GNU汇编器(AS)、GNU链接器(LD)、GNU调试器(GDB)等,它们在编译和调试过程中发挥重要作用。总的来说,GCC是一套功能强大的编译器工具集,可用于编译和构建各种编程语言的源代码,并生成可执行的机器代码。

三.  编写一个C程序,重温全局常量、全局变量、局部变量、静态变量、堆、栈等概念,在Ubuntu(x86)系统和STM32(Keil)中分别进行编程、验证(STM32 通过串口printf 信息到上位机串口助手) 。1)归纳出Ubuntu、stm32下的C程序中堆、栈、全局、局部等变量的分配地址,进行对比分析;2)加深对ARM Cortex-M/stm32F10x的存储器地址映射的理解。下图是一个Cortex-M4的存储器地址映射示意图(与Cortex-M3/stm32F10x基本相同,只存在微小差异)

程序示例:

执行结果:

归纳与总结:

对于Ubuntu(x86)系统:

  • 全局常量和全局变量通常存储在数据段或BSS段,其地址在程序运行时是固定的。
  • 局部变量和静态变量存储在栈中,其地址在程序运行时动态分配。
  • 动态分配的内存(堆)使用malloc函数,在堆区分配内存,其地址也是在程序运行时动态确定的。

对于STM32(Keil):

  • 全局常量和全局变量存储在Flash存储器中,其地址在编译时确定并存储在Flash中。
  • 局部变量和静态变量存储在栈中,其地址在程序运行时动态分配。
  • 动态分配的内存(堆)使用malloc函数,在SRAM或SDRAM区分配内存,其地址也是在程序运行时动态确定的。

通过上述程序,你可以在Ubuntu上使用printf输出变量的地址,而在STM32上,你可以使用串口printf将变量的地址打印到上位机串口助手上。这样就可以进行对比分析,加深对于堆、栈、全局和局部变量在不同平台下的分配地址的理解。

至于ARM Cortex-M / STM32F10x的存储器地址映射,它将不同类型的存储器(如Flash、RAM等)划分到不同的地址空间。通过正确理解存储器地址映射,我们可以正确分配和访问变量,并与相应的寄存器进行交互,以实现所需的功能。对于具体的地址映射细节,你可以参考芯片的数据手册来了解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值