C程序中Ubuntu、stm32的內存分配問題

一、內存分區概念介紹

1.1、C/C++編譯程序的內存占用

1、棧區(stack)
由編譯器自動分配釋放,存放函數的參數值,局部變量的值等。其操作方式類似於數據結構中的棧。
2、堆區(heap)
一般由程序員分配釋放,若程序員不釋放,程序結束時可能由OS回收 。它與數據結構中的堆不同,分配方式類似於鏈表。
3、全局區(靜態區)(static)
全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域, 未初始化的全局變量、未初始化的靜態變量在相鄰的另一塊區域。當程序結束後,變量由系統釋放 。
4、文字常量區
存放常量字符串。當程序結束後,常量字符串由系統釋放 。
5、程序代碼區
存放函數體的二進制代碼。

1、從靜態存儲區域分配。內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在。例如全局變量,static變量。

2、在棧上創建。在執行函數時,函數內局部變量的存儲單元都可以在棧上創建,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置於處理器的指令集中,效率很高,但是分配的內存容量有限。
3、從堆上分配,亦稱動態內存分配。程序在運行的時候用malloc或new申請任意多少的內存,程序員自己負責在何時用free或delete釋放內存。動態內存的生存期由程序員決定,使用非常靈活,但如果在堆上分配瞭空間,就有責任回收它,否則運行的程序會出現內存泄漏,頻繁地分配和釋放不同大小的堆空間將會產生堆內碎塊。

1.2、棧和堆、全局/靜態存儲區和常量存儲區的對比

1、棧區: 主要用來存放局部變量, 傳遞參數, 存放函數的返回地址。.esp 始終指向棧頂, 棧中的數據越多, esp的值越小。

2、堆區: 用於存放動態分配的對象, 當你使用 malloc和new 等進行分配時,所得到的空間就在堆中。動態分配得到的內存區域附帶有分配信息, 所以你能夠 free和delete它們。

3、數據區: 全局,靜態和常量是分配在數據區中的,數據區包括bss(未初始化數據區)和初始化數據區。  

註意: 堆向高內存地址生長; 棧向低內存地址生長; 堆和棧相向而生,堆和棧之間有個臨界點,稱為stkbrk。

1.3、圖片說明

 

補充:

Stack: 棧,存放Automatic Variables,按內存地址由高到低方向生長,其最大大小由編譯時確定,速度快,但自由性差,最大空間不大。

Heap: 堆,自由申請的空間,按內存地址由低到高方向生長,其大小由系統內存/虛擬內存上限決定,速度較慢,但自由性大,可用空間大。
每個線程都會有自己的棧,但是堆空間是共用的。

參考文獻:https://www.icode9.com/content-1-772915.html 

 二、C語言編程論證

1.1、Ubuntu測試代碼實現

1、main.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] = "lyy";
    //定義常量字符串
    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;
}

2、使用命令創建一個 main.c 文件:

gedit main.c

3、復制粘貼上述代碼

4、編譯執行

gcc main.c -o main

./main

5、結果展示

 

分析說明:可以從上圖可以得出棧區內存地址由高到低方向生長,堆區內存地址由低到高方向生長。而且整個程序的內存也是從高到低的地址進行分配的。 

 1.2、STM32驗證代碼實現

打開之前做過的串口通訊實驗代碼,在其中進行修改,後面我也會給出先關串口鏈接

1、代碼更改

修改bsp_usart.h :

添加頭文件:

#include <stdio.h>
#include <stdlib.h>

 修改bsp_usart.c :

重寫 fputc 函數:

int fputc(int ch, FILE *f)
{
	USART_SendData(DEBUG_USARTx, (uint8_t)ch);
	
	while(USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);
	
	return (ch);
}

main.c : 

#include "stm32f10x.h"
#include "bsp_usart.h"  //添加 bsp_usart.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)
{	
	//定義局部變量
	int a=2;
	static int inits_local_c=2, uninits_local_c;
	int init_local_d = 1;
	char *p;
	char str[10] = "lyy";
	//定義常量字符串
	char *var1 = "1234567890";
	char *var2 = "qwertyuiop";
	//動態分配
	int *p1=malloc(4);
	int *p2=malloc(4);
	USART_Config();//串口初始化
	output(a);
	//釋放
	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、編譯輸出

3、燒錄

打開串口調試助手,打開串口後,按一下 RESET 鍵,顯示結果:

 4、分析說明

   與Ubuntu一樣,stm32的棧區的地址值是從上到下減小的,堆區則是從上到下增長的。從每個區來看,地址值是從上到下逐步減小的,即棧區的地址是高地址,代碼區的地址是處於低地址。

1.3、keil下stm32存儲觀察

stm32數據的存儲位置

1、RAM(隨機存取存儲器)
存儲的內容可通過指令隨機讀寫訪問。RAM中的存儲的數據在掉電是會丟失,因而隻能在開機運行時存儲數據。其中RAM又可以分為兩種,一種是Dynamic RAM(DRAM動態隨機存儲器),另一種是Static RAM(SRAM,靜態隨機存儲器)。棧、堆、全局區(.bss段、.data段)都是存放在RAM中。
2、ROM(隻讀存儲器)
隻能從裡面讀出數據而不能任意寫入數據。ROM與RAM相比,具有讀寫速度慢的缺點。但由於其具有掉電後數據可保持不變的優點,因此常用也存放一次性寫入的程序和數據,比如主版的BIOS程序的芯片就是ROM存儲器。代碼區和常量區的內容是不允許被修改的,所以存放於ROM中。

查看:

分析說明:

    從圖片中可以看出ROM的地址分配是從0x8000000開始,整個大小為0x40000,這個部分用於存放代碼區和文字常量區。RAM的地址分配是從0x20000000開始,其大小是0xC000,這個區域用來存放棧、堆、全局區(.bss段、.data段)。與代碼結果顯示進行對比,也可以看出對應得部分得地址與設置的是相對應的。 

三、總結

   通過對C語言程序裡全局變量、局部變量、堆、棧等概念的重溫以及在不同平臺進行編程驗證,熟悉掌握瞭C語言中相關概念,並對整體的內存地址分配由高到低,以及棧區內存地址由高到低方向生長,堆區內存地址由低到高方向生長進行瞭驗證。經過本次實驗,主要是對C程序的內存分配有進一步的認識,知道一個C程序內存應該包括哪些部分。其中,主要是程序段、數據段、堆棧三個部分。不同系統下面,區域內的地址值變化是不相同。總的來說,是對內存的分配有瞭比較新的認識。

四、參考資料

https://blog.csdn.net/qq_43279579/article/details/110308101

https://blog.csdn.net/ssj925319/article/details/110727925?spm=1001.2014.3001.5501 

USART串口通信:

鏈接: https://pan.baidu.com/s/1YspOK2I3Ddm5XntKXKRnXA

提取碼: emev 

到此這篇關於C程序中Ubuntu、stm32的內存分配問題的文章就介紹到這瞭,更多相關C程序Ubuntu、stm32的內存分配內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: