嵌入式开发03 gcc背后的故事

前言

源文件经过编译器编译后才可生成二进制文件,编译过程包括预处理、编译、汇编和链接,日常交流中常用“编译”称呼此四个过程。编译器是一系列工具的集合,如arm平台使用的交叉编译器arm-linux-gcc包括arm-linux-cpp(compiler preprocessor预处理)、arm-linux-cc1(c compiler编译)、arm-linux-as(assembly 汇编)、arm-linux-ld(linker链接)等工具。常见的编译器种类如:GCC、VC等\n我们这次就以GCC为例,学习编译器背后的故事

一、可执行程序的组装

一个源程序到一个可执行程序需要以下步骤:预编译、编译、汇编、链接。
其中,编译是主要部分,其中又包括:词法分析、语法分析、语义分析、中间代码生成、目标代码生成和优化六个部分。
链接部分分为静态链接和动态链接,本文主要讲解静态链接。
1.预编译:主要处理源代码文件中“#”开头的预编译指令
2.编译:把预编译后生成的xxx.i或xxx.ii文件进行一系列词法分析、语法分析、语义分析及优化后,生成相应的汇编代码文件。
3.汇编:将汇编代码转变成机器可以执行的指令(机器码文件)
4.链接:具体要深入了解可以访问该下博客:链接

1.1 用gcc链接mian 1的目标文件与静态库文件

1.首先建立sub2.c文件
在这里插入图片描述
2.使用gedit sub2.c命令对sub2.c文件进行编辑,代码如下

#include<stdio.h>
float x2y(int a,int b){
    float d;
    d=a-b+1;
    return d;
}

3.打开之前编写的main.c文件,进入编辑界面,修改代码如下:

#include<stdio.h>
extern float x2x(int a,int b);
extern float x2y(int a,int b);
int main()
{
	float sum,sum1;
	int a=5,b=10;
	sum = x2x(a,b);
    sum1= x2y(a,b);
	printf("sum=%f\n",sum);
	printf("sum1=%f\n",sum1);
	return 0;
}

4.使用命令gcc -c将sub1.c sub2.c main.c分别编译成对应的.o文件
在这里插入图片描述
5.使用ar工具将x2x、x2y目标文件生成一个.a静态库文件
在这里插入图片描述
6.用gcc将main函数的目标文件与此静态库文件libsub.c进行链接,命令如下:
在这里插入图片描述
7. 输入./main执行程序,得到输出结果
在这里插入图片描述

1.2 用gcc生成静态库与动态库

  1. 首先建立动态库libsub.so,用ls命令查看是否建立成功
    在这里插入图片描述

  2. 链接动态库生成可执行文件
    在这里插入图片描述

  3. 执行程序
    在这里插入图片描述

二、stm32的内存分配问题

2.1 栈区

临时创建的局部变量存放在栈区。
函数调用时,其入口参数存放在栈区。
函数返回时,其返回值存放在栈区。
const定义的局部变量存放在栈区

2.2 堆区

堆区用于存放程序运行中被动态分布的内存段,可增可减。
可以有malloc等函数实现动态分布内存。
有malloc函数分布的内存,必须用free进行内存释放,否则会造成内存泄漏。

全局区

(1).bss段

未初始化的全局变量存放在.bss段。
初始化为0的全局变量和初始化为0的静态变量存放在.bss段。
bss段不占用可执行文件空间,其内容有操作系统初始化。

(2).data段

已经初始化的全局变量存放在.data段。
静态变量存放在.data段。
.data段占用可执行文件空间,其内容有程序初始化。
const定义的全局变量存放在.rodata段。

2.3常量区

字符串存放在常量区。
常量区的内容不可以被修改。

2.4代码区

程序执行代码存放在代码区。
字符串常量也有可能存放在代码区。

2.5 RAM和ROM、Flash Memory的物理特性

(1)RAM

RAM又称随机存取存储器,存储的内容可通过指令随机读写访问。RAM中的存储的数据在掉电是是会丢失,因而只能在开机运行时存储数据。其中RAM又可以分为两种,一种是Dynamic RAM(DRAM动态随机存储器),另一种是Static RAM(SRAM,静态随机存储器)。

(2)ROM

ROM又称只读存储器,只能从里面读出数据而不能任意写入数据。ROM与RAM相比,具有价格高,容量小的缺点。但由于其具有掉电后数据可保持不变的优点,因此常用也存放一次性写入的程序和数据,比如主版的BIOS程序的芯片就是ROM存储器。

(3)Flash Memory

由于ROM具有不易更改的特性,后面就发展了Flash Memory。Flash Memory不仅具有ROM掉电不丢失数据的特点,又可以在需要的时候对数据进行更改,不过价格比ROM要高。

实验过程

Ubuntu(X86)系统中验证

1.代码
#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.运行结果

在这里插入图片描述

Keil下验证

1.keli环境默认内存配置

在这里插入图片描述

① 默认分配的ROM区域是0x8000000开始,大小是0x80000的一片区域,那么这篇区域是只读区域,不可修改,也就是存放的代码区和常量区
② 默认分配的RAM区域是0x20000000开始,大小是0x10000的一片区域,这篇区域是可读写区域,存放的是静态区、栈区和堆区。

2.代码
#include "sys.h"
#include "usart.h"		
#include "delay.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;
	Stm32_Clock_Init(9);	//??????
	delay_init(72);	  		//?????
	uart_init(72,115200); 	//??????115200
  	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;
	}	 
} 

3.结果

在这里插入图片描述

总结

一般而言,程序内变量在堆栈上的分配,栈是由高地址到低地址,堆是由低地址到高地址。

在Ubuntu下,栈区的地址存储是向上增长,堆区的地址存储也是向上增长;
在STM32下,栈区的地址存储是向下增长,堆区的地址存储却是向上增长。

可是为什么Ubuntu下,栈区的地址值也是增长的?
查找了很多资料发现,大部分提到linux的栈,地址都是向下生长的,不过,也有以下解释:

第一种解释:
栈向低地址扩展(即”向下生长”),是连续的内存区域;堆向高地址扩展(即”向上生长”),是不连续的内存区域。

这是由于系统用链表来存储空闲内存地址,自然不连续,而链表从低地址向高地址遍历。

第二种解释:(暂时放到下面,还需要花时间理解分析这一点)

进程地址空间的分布取决于操作系统,栈向什么方向增长取决于操作系统与CPU的组合。
这里说的“栈”是函数调用栈,是以“栈帧”(stack frame)为单位的。
每一次函数调用会在栈上分配一个新的栈帧,在这次函数调用结束时释放其空间。
被调用函数(callee)的栈帧相对调用函数(caller)的栈帧的位置反映了栈的增长方向:如果被调用函数的栈帧比调用函数的在更低的地址,那么栈就是向下增长;反之则是向上增长。

在一个栈帧内,局部变量是如何分布到栈帧里的(所谓栈帧布局,stack frame layout),这完全是编译器的自由。

在简化的32位Linux/x86进程地址空间模型里,(主线程的)栈空间确实比堆空间的地址要高——它已经占据了用户态地址空间的最高可分配的区域,并且向下(向低地址)增长。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值