gcc相关运行原理及buntu、stm32下的程序内存分配问题
一、任务要求
一. 学习并掌握可执行程序的编译、组装过程。学习任务如下:
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函数的目标文件与此动态库文件进行链接,生成最终的可执行程序,记录文件的大小,并与之前做对比。
二. Gcc不是一个人在战斗。请说明gcc编译工具集中各软件的用途,了解EFF文件格式。学习任务如下:阅读、理解和学习材料“Linux GCC常用命令.pdf”和“GCC编译器背后的故事.pdf”,如实仿做一遍。
三.编写一个C程序,重温全局变量、局部变量、堆、栈等概念,在Ubuntu(x86)系统和STM32(Keil)中分别进行编程、验证(STM32 通过串口printf 信息到上位机串口助手) 。
归纳出Ubuntu、stm32下的C程序中堆、栈、全局、局部等变量的分配地址,进行对比分析。
二、实验过程
2.1学习并掌握可执行程序的编译、组装过程
2.1.1gcc生成可执行的动态静态库
1、创建hello.h、hello.c 和 main.c文件,输入以下代码
程序 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、将hello.c编译成.o文件
gcc -c hello.c
3、创建静态库
创建静态库用 ar 命令。在系统提示符下键入以下命令将创建静态库文件 libmyhello.a
ar -crv libmyhello.a hello.o
4、程序中使用静态数据库
gcc -o main main.c libmyhello.a
5、建立s文件
gcc -c -fpic hello.c gcc -shared *.o -o libsofile.s
6、程序使用动态库
mv libsofile.so /user/lib idconfig
编译运行,程序出现报错。
解决方案:将文件 libmyhello.so 复制到目录
2.1.2生成静态和动态文件并进行链接
1、创建sub1.h、sub2.h、main.c、sub1.c、sub2.c文件:
vi main.c vi sub1.h vi sub2.h vi sub1.c vi sub2.c
对应文件内容如下:
sub1.h:
#include<stdio.h>
int add(int a,int b);
sub2.h:
#include<stdio.h>
int minux(int a,int b);
sub1.c:
#include"sub1.h"
int add(int a,int b){
return a+b;
}
sub2.c:
#include"sub1.h"
int minus(int a,int b){
return a-b;
}
main.c:
#include<stdio.h>
#include"sub1.h"
#include"sub2.h"
int main()
{
int a =1,b =2;
int c = add(a,b);
int d = minus(a,b);
printf("%d\n",c);
printf("%d",d);
return 0;
}
2、生成静态库a
gcc -c sub1.c sub2.c
ar crv libafile.a sub1.o sub2.o
使用.a文件创建可执行程序并执行
gcc -o main main.c libafile.a ./main
结果:3、-1
动态库.so文件使用
生成目标文件
gcc -c -fpic sub1.c sub2.c
生成共享.so文件
gcc -shared *.o -o libsofile.so
使用.so库文件,创建可执行程序
gcc -o test main.c libsofile.so ./test
程序出现报错!
解决办法:只在/lib and /usr/lib 下搜索对应 的.so 文件,故需将对应 so 文件拷贝到对应的路径就行了
sudo cp libsofile.so /user/lib
2.2Gcc不是一个人在战斗。请说明gcc编译工具集中各软件的用途,了解EFF文件格式
1、在Linux系统下安装gcc
yum install gcc
2、创建main.c
vi main.c
main.c文件内容如下
#include <stdio.h> //此程序很简单,仅仅打印一个 Hello World 的字符串。 int main(void) { printf("Hello World! \n"); return 0; }
3、进行预处理过程
将main.c转化为main.i文件
gcc -E main.c -o main.i
4、编译过程
将我们处理的mian.c文件得到的main.i文件做进一步的处理,得到main.s的文件
gcc -S main.i -o main.s
5、汇编
将main.s文件转化为main.o文件
gcc -c main.s -o main.o -v
6、动态链接和静态链接
(1) 静态链接是指在编译阶段直接把静态库加入到可执行文件中去,这样可执行 文件会比较大。链接器将函数的代码从其所在地(不同的目标文件或静态链 接库中)拷贝到最终的可执行程序中。为创建可执行文件,链接器必须要完 成的主要任务是:符号解析(把目标文件中符号的定义和引用联系起来)和 重定位(把符号定义和内存地址对应起来然后修改所有对符号的引用)。
(2) 动态链接则是指链接阶段仅仅只加入一些描述信息,而程序执行时再从系统 中把相应动态库加载到内存中去
执行动态链接
gcc main.c -o main
执行静态链接
gcc -static main.c -o main
使用ldd指令看是否链接 了动态库
ldd main
2.3Ubuntu、stm32下的程序内存分配问题
2.31全局变量 & 局部变量
全局变量
在所有函数外部定义的变量称为全局变量(Global Variable),它的作用域默认是整个程序,也就是所有的源文件。
局部变量
定义在函数内部的变量称为局部变量(Local Variable),它的作用域仅限于函数内部, 离开该函数的内部就是无效的,再使用就会报错。
2.32堆 & 栈
1、STM32中的堆栈
单片机是一种集成电路芯片,集成CPU、RAM、ROM、多种I/O口和中断系统、定时器/计数器等功能。CPU中包括了各种总线电路,计算电路,逻辑电路,还有各种寄存器。
stm32 有通用寄存器 R0‐ R15 以及一些特殊功能寄存器,其中包括了堆栈指针寄存器。
当stm32正常运行程序的时候,来了一个中断,CPU就需要将寄存器中的值压栈到RAM里,然后将数据所在的地址存放在堆栈寄存器中。
等中断处理完成退出时,再将数据出栈到之前的寄存器中,这个在C语言里是自动完成的。
2、程序的内存分配
一般程序占用的内存分为以下几个部分:
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收。它与数据结构中的堆是两回事,分配方式类似于链表。
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 程序结束后有系统释放
4、文字常量区—常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区—存放函数体的二进制代码。
程序如下:
//main.cpp int a = 0; //全局初始化区 int a = 0; //全局初始化区 char *p1; //全局未初始化区 main() { int b; //栈 char s[] = "abc"; //栈 char *p2; //栈 char *p3 = "123456"; //123456\0在常量区,p3在栈上。 static int c = 0; //全局(静态)初始化区 p1 = (char *)malloc(10); p2 = (char *)malloc(20); //分配得来得10和20字节的区域就在堆区。 strcpy(p1, "123456"); //123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。 }
2.33Ubuntu(x86)系统和STM32(Keil)中编程验证
编码编写:
#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] = "yaoyao";//栈
//定义常量字符串
char *var1 = "1234567890";
char *var2 = "abcdefghij";
//动态分配——堆区
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;
}
2、Ubuntu运行
将上面的代码放入nano文本编辑器中,进行编译
编译结果:Ubuntu在栈区和堆区的地址值都是从上到下增长的
3、Keil运行
keil 环境下默认的内存配置说明
① 默认分配的ROM区域是0x8000000开始,大小是0x80000的一片区域,那么这篇区域是只读区域,不可修改,也就是存放的代码区和常量区
修改主函数:
#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "usart.h"
#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(void)
{
u16 t;
u16 len;
u16 times=0;
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
LED_Init(); //LED端口初始化
KEY_Init(); //初始化与按键连接的硬件接口
while(1)
{
//定义局部变量
int a=2;
static int inits_local_c=2, uninits_local_c;
int init_local_d = 1;
output(a);
char *p;
char str[10] = "yaoyao";
//定义常量字符串
char *var1 = "1234567890";
char *var2 = "abcdefghij";
//动态分配
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;
}
}
编译报错
原因:声明不能出现在可执行状态之后,C语言关于变量的定义只能放在函数的开头,放在执行语句的前面定义,这是C89的标准。
后来的C99标准就已经改变了,无论定义在之前还是之后都是可以的。
解决办法:点击魔术棒,再点c/c++,打钩上C99 mode ,再使用微库
再次编译,成功!
将代码烧录进入芯片
按下reset键,单片机发送数据
实验结果:stm32的栈区的地址值是从上到下减小的,堆区则是从上到下增长的。
4、结果分析(仍在思考ing)
一般而言,程序内变量在堆栈上的分配,栈是由高地址到低地址,堆是由低地址到高地址。
在Ubuntu下,栈区的地址存储是向上增长,堆区的地址存储也是向上增长;
在STM32下,栈区的地址存储是向下增长,堆区的地址存储却是向上增长
参考:【1】[gcc相关运行原理及linux系统下opencv使用_opencv要装gcc吗_辣子鸡味的橘子的博客-CSDN博客](https://blog.csdn.net/qq_52548731/article/details/126950628?ops_request_misc=&request_id=&biz_id=102&utm_term=一. 学习并掌握可执行程序的编译、组装过程。学习任务如下:&utm_medium=distribute.pc_search_result.none-task-blog-2blogsobaiduweb~default-0-126950628.nonecase&spm=1018.2226.3001.4450)
分配,栈是由高地址到低地址,堆是由低地址到高地址。
在Ubuntu下,栈区的地址存储是向上增长,堆区的地址存储也是向上增长;
在STM32下,栈区的地址存储是向下增长,堆区的地址存储却是向上增长
参考:【1】[gcc相关运行原理及linux系统下opencv使用_opencv要装gcc吗_辣子鸡味的橘子的博客-CSDN博客](https://blog.csdn.net/qq_52548731/article/details/126950628?ops_request_misc=&request_id=&biz_id=102&utm_term=一. 学习并掌握可执行程序的编译、组装过程。学习任务如下:&utm_medium=distribute.pc_search_result.none-task-blog-2blogsobaiduweb~default-0-126950628.nonecase&spm=1018.2226.3001.4450)
【2】【嵌入式18】Ubuntu、stm32下的程序内存分配问题(堆栈、局部全局变量等)_ubuntu软件使用内存位置_噗噗的罐子的博客-CSDN博客