目录
4、linux常用命令的仿做与gcc编译工具集中各软件的用途
一、题目要求
1. 学习并掌握可执行程序的编译、组装过程。学习任务如下:
1)阅读、理解和学习材料“用gcc生成静态库和动态库.pdf”和“静态库.a与.so库文件的生成与使用.pdf”,请在Linux系统(Ubuntu)下如实仿做一遍。
2)在第一次作业的程序代码基础进行改编,除了x2x函数之外,再扩展写一个x2y函数(功能自定),main函数代码将调用x2x和x2y ;将这3个函数分别写成单独的3个 .c文件,并用gcc分别编译为3个.o 目标文件;将x2x、x2y目标文件用 ar工具生成1个 .a 静态库文件, 然后用 gcc将 main函数的目标文件与此静态库文件进行链接,生成最终的可执行程序,记录文件的大小。
3)将x2x、x2y目标文件用 ar工具生成1个 .so 动态库文件, 然后用 gcc将 main函数的目标文件与此动态库文件进行链接,生成最终的可执行程序,记录文件的大小,并与之前做对比。
2. Gcc不是一个人在战斗。请说明gcc编译工具集中各软件的用途,了解EFF文件格式。学习任务如下:阅读、理解和学习材料“Linux GCC常用命令.pdf”和“GCC编译器背后的故事.pdf”,如实仿做一遍。
3.编写一个C程序,重温全局常量、全局变量、局部变量、静态变量、堆、栈等概念,在Ubuntu(x86)系统和STM32(Keil)中分别进行编程、验证(STM32 通过串口printf 信息到上位机串口助手) 。1)归纳出Ubuntu、stm32下的C程序中堆、栈、全局、局部等变量的分配地址,进行对比分析;2)加深对ARM Cortex-M/stm32F10x的存储器地址映射的理解。
二、过程呈现
1、用gcc生成静态库和动态库
1)代码文件准备
hello代码
我们可以在终端上使用vim对hello代码进行编写分别输入vi hello.h,vi hello.c,vi main.c,在终端上输入如下
具体代码如下
hello.h
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
#endif//HELLO_H
hello.c
#include<stdio.h>
void hello(const char *name)
{
printf("Hello %s\n",name);
}
main.c
#include"hello.h"
int main()
{
hello("everyone");
return 0;
}
接下来我们便使用gcc编译得到.o文件,终端输入gcc -c hello.c,具体操作如下
2)静态库的使用
(1)静态库的创建与使用
我们在终端输入ar -crv libmyhello.a hello.o即可创建静态库
然后我们在终端输入以下命令
gcc -o hello main.c -L. -lmyhello
然后编译程序可得到输出,如下
(2)静态库的特点验证
若删除静态库文件,在运行文件,我们发现其仍然可以正常运行,则表明静态库与程序执行文件没有联系,如下
3)动态库的使用
动态库的创建
终端输入gcc -shared -fPIC -o libmyhello.so hello.o即可创建动态库我们也可在终端输入ls查看是否创建完毕以及创建目录,具体步骤演示如下
动态库的使用
此时我们输入./hello即可输出所编译的结果
2、静态库文件与动态库文件的生成与使用
1)代码准备
与前面一样,我们还是在vim上面完成相关程序的编写,终端输入如下代码
vi A1.c,
vi A2.c,
vi A.h
vi test.c
在此之前我们可以新建一个目录,并将上诉程序放在该目录下
2)静态库的生成与使用
我们在终端输入ar crv libfile.a A1.o A2.o,然后输入gcc -o test test.c libfile.a,,具体步骤如下
此时出现错误,我们要将test文件中的exit(0)改为return 0即可,然后输入./test可得到输出
3)动态库的使用
输入
gcc -shared -fPIC -o libfile.so A1.o A2.o
gcc -o test test.c libfile.so
./test
具体步骤与结果如下
3、x2x、x2y程序生成静态库文件与动态文件并进行链接
1)代码实现
先创建一个新目录test3,并将要编写的代码放在该目录下
x2x此前实验已经编译好
输入vi sub2.c
编写如下代码
float x2y(int a,int b)
{
float c=0;
c=a/b;
return c;
}
输入vi sub.h,编写如下代码
#ifndef SUB_H
#define SUB_H
float x2x(int a,int b);
float x2y(int a,int b);
#endif
输入vi main.c
#include<stdio.h>
#include"sub.h"
void main()
{
int a,b;
printf("Please input the value of a:");
scanf("%d",&a);
printf("Please input the value of b:");
scanf("%d",&b);
printf("a+b=%.2f\n",x2x(a,b));
printf("a/b=%.2f\n",x2y(a,b));
}
终端输入gcc -c sub1.c sub2.c
2)生成静态库
输入ar crv libsub.a sub1.o sub2.o
gcc -o main main.c libsub.a
我们还可以输入./main完成静态库的使用,运行程序,上述的全部操作如下所示
3)生成动态库
终端输入
gcc -shared -fPIC libsub.so sub1.o sub2.o
gcc -o main main.c libsub.so
4)与之前做对比
静态库
动态库
4、linux常用命令的仿做与gcc编译工具集中各软件的用途
1)gcc编译工具集中各软件的用途
GCC:
GCC(GNU C Compiler)是编译工具。本文所要介绍的将 C/C++语言编写的程序转换成为处理器能够执行的二进制代码的过程即由编译器完成。
Binutils:
一组二进制程序处理工具,包括:addr2line、ar、objcopy、objdump、as、ld、ldd、readelf、 size 等。这 一组工具 是开发和 调试不可 缺少的工具,分别简介如下:
(1) addr2line:用 来将程序 地址转 换成其所 对应的程 序源文件及所对应的代码行,也可以得到所对应的函数。该工具将帮助调试器在调试的过程中定位对应的源代码位置。
(2) as:主要用于汇编,有关汇编的详细介绍请参见后文。
(3) ld:主要用于链接,有关链接的详细介绍请参见后文。
(4) ar:主要用于创建静态库。为了便于初学者理解,在此介绍动态库与静态库的概念:
(5) ldd:可以用于查看一个可执行程序依赖的共享库。
(6) objcopy:将一种对象文件翻译成另一种格式,譬如将.bin 转换成.elf、或者将.elf 转换成.bin 等。
(7) objdump:主要的作用是反汇编。有关反汇编的详细介绍,请参见后文。
(8) readelf:显示有关 ELF 文件的信息,请参见后文了解更多信息。
(9) size:列出可执行文件每个部分的尺寸和总尺寸,代码段、数据段、总大小等,请参见后文了解使用 size
C 运行库
C 语言标准主要由两部分组成:一部分描述 C 的语法,另一部分描述C 标准库。C 标准库定义了一组标准头文件,每个头文件中包含一些相关的函数、变量、类型声明和宏定义,譬如常见的 printf 函数便是一个 C 标准库函数,其原型定义在 stdio 头文件中。 C 语言标准仅仅定义了 C 标准库函数原型,并没有提供实现。因此,C 语言编译器通常需要一个 C 运行时库(C Run Time Libray,CRT)的支持。C 运行时库又常简称为 C 运行库。与 C 语言类似,C++也定义了自己的标准,同时提供相关支持库,称为 C++运行时库。
2)linux常用命令的仿做
简单编译
示例程序使用vim进行编写,输入
vi test.c,程序如下
#include <stdio.h>
int main(void)
{
printf("Hello World!\n");
return 0;
}
其一步到位的编译指令为
gcc test.c -o test
终端输入gcc -E test.c -o test.i 或 gcc -E test.c,进行预处理
终端输入gcc -S test.i -o test.s编译为汇编代码
终端输入gcc -c test.s -o test.o进行汇编
终端输入gcc test.o -o test可以进行连接
所有步骤如下所示
5、C程序变量常量的地址分配
1)Ubuntu系统验证
Ubuntu系统仍然使用vim对程序进行编译
终端输入vi text.c,编写如下代码
#include <stdio.h>
#include <stdlib.h>
//定义全局变量
int init_global_a = 1;
int uninit_global_a;
static int inits_global_b = 2;
static int uninits_global_b;
void output(int a)
{
printf("hello");
printf("%d",a);
printf("\n");
}
int main( )
{
//定义局部变量
int a=2;
static int inits_local_c=2, uninits_local_c;
int init_local_d = 1;
output(a);
char *p;
char str[10] = "lmy";
//定义常量字符串
char *var1 = "1234567890";
char *var2 = "qwertyuiop";
//动态分配
int *p1=malloc(4);
int *p2=malloc(4);
//释放
free(p1);
free(p2);
printf("栈区-变量地址\n");
printf(" a:%p\n", &a);
printf(" init_local_d:%p\n", &init_local_d);
printf(" p:%p\n", &p);
printf(" str:%p\n", str);
printf("\n堆区-动态申请地址\n");
printf(" %p\n", p1);
printf(" %p\n", p2);
printf("\n全局区-全局变量和静态变量\n");
printf("\n.bss段\n");
printf("全局外部无初值 uninit_global_a:%p\n", &uninit_global_a);
printf("静态外部无初值 uninits_global_b:%p\n", &uninits_global_b);
printf("静态内部无初值 uninits_local_c:%p\n", &uninits_local_c);
printf("\n.data段\n");
printf("全局外部有初值 init_global_a:%p\n", &init_global_a);
printf("静态外部有初值 inits_global_b:%p\n", &inits_global_b);
printf("静态内部有初值 inits_local_c:%p\n", &inits_local_c);
printf("\n文字常量区\n");
printf("文字常量地址 :%p\n",var1);
printf("文字常量地址 :%p\n",var2);
printf("\n代码区\n");
printf("程序区地址 :%p\n",&main);
printf("函数地址 :%p\n",&output);
return 0;
}
在Ubuntu系统中如下
终端输入
gcc test.c -o text
./test
输出结果如下
Ubuntu在栈区和堆区的地址值都是从上到下增长的
2)stm32下验证
在keil中进行改写
编译,如下
STM32 在栈区和堆区的地址值是从上往下的在减小与Ubuntu下刚好相反。
三、总结
这次作业的内容有点多,对自己来说难度也较大,有些方面自己完成的也不是很好,还是需要查阅参考资料辅助自己才可以完成较多内容,不过总的来时,它对我的提升还是较大的,希望下次可以做到更好。