Ubuntu、stm32下的C程序中堆、栈、全局、局部等变量的分配地址,进行对比分析

本文详细解释了堆、栈、全局变量、局部变量的区别,以及在Ubuntu和STM32环境下C程序的内存分配方式。特别关注了ARMCortex-M系列和STM32F10x的存储器地址映射。通过实例展示了如何在编程中验证这些概念。

一、堆、栈、全局、局部等变量的概念

1.全局变量

在所有函数外部定义的变量称为全局变量(Global Variable),它的作用域默认是整个程序,也就是所有的源文件。

2.局部变量

定义在函数内部的变量称为局部变量(Local Variable),它的作用域仅限于函数内部, 离开该函数的内部就是无效的,再使用就会报错。

全局变量局部变量
定义位置在方法外部,直接写在类中在方法内部
作用范围整个类中都可以使用只能在方法中使用
默认值如果没有赋值,则有默认值,规则同数组没有默认值,要使用必须手动赋值
内存位置位于堆内存位于栈内存
3.堆、栈

在这里插入图片描述

  • 栈区(stack):指那些由编译器在需要的时候分配,不需要时自动清除的变量所在的储存区,如函数执行时,函数的形参以及函数内的局部变量分配在栈区,函数运行结束后,形参和局部变量去栈(自动释放)。栈内存分配运算内置与处理器的指令集中,效率高但是分配的内存空间有限。
  • 堆区(heap):指哪些由程序员手动分配释放的储存区,如果程序员不释放这块内存,内存将一直被占用,直到程序运行结束由系统自动收回,c语言中使用malloc,free申请和释放空间。
  • 静态存储区(static):全局变量和静态变量的储存是放在一块的,其中初始化的全局变量和静态变量在一个区域,这块空间当程序运行结束后由系统释放。
  • 常量存储区(const):常量字符串就是储存在这里的,如“ABC”字符串就储存在常量区,储存在常量区的只读不可写。const修饰的全局变量也储存在常量区,const修饰的局部变量依然在栈上。程序代码区:存放源程序的二进制代码。
4.堆栈的对比

在这里插入图片描述

5.在Ubuntu、stm32下C程序分配地址的对比

在这里插入图片描述

6.对ARM Cortex-M/stm32F10x的存储器地址映射的理解

在ARM Cortex-M系列中,存储器地址映射通常遵循以下原则:

  • 代码存储器: Flash存储器用于存储程序代码,通常被映射到处理器的地址空间的起始位置。该映射可以是连续的或分段的,取决于具体的处理器和片内存储器的布局。
  • 数据存储器: 数据存储器包括SRAM(随机访问存储器)和外设寄存器,用于存储变量和外设的配置寄存器。SRAM通常映射到处理器地址空间的中间区域,而外设寄存器则映射到一个独立的地址空间,通常称为Peripherals区域。
  • 堆栈: 堆栈用于存储函数调用的局部变量和返回地址等信息。在ARM Cortex-M系列中,堆栈位于SRAM的末尾,使用较高的地址。
  • 特殊寄存器: ARM Cortex-M系列处理器还有一些特殊的寄存器,如NVIC(中断控制器)和SCB(系统控制块)。这些寄存器通常被映射到特定的地址,用于控制中断、系统时钟和处理器状态等。

stm32F10x系列单片机在ARM Cortex-M系列的基础上,还具有自己的存储器布局和外设寄存器映射。具体的映射关系和地址范围可以在芯片的参考手册中找到,手册会指定Flash、SRAM、寄存器和其他外设的起始地址和范围,以及访问这些地址时的读写权限和功能描述。

二、编程验证

1.Ubantu系统

  • 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] = "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;}

  • 输入指令:

gcc text.c -o text
./text

  • 输出结果如下:
    在这里插入图片描述
    在这里插入图片描述

2.Keil stm32

  • 新建工程text.c
#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){
	uart_init(72,115200);  	
	while(1)  {  	//定义局部变量  	
	int a=2;  	
	static int inits_local_c=2,uninits_local_c;  	
	int init_local_d = 1;  	
	char *p;  	
	char str[10] = "zls";  	//定义常量字符串  	
	char *var1 = "1234567890";  	
	char *var2 = "qwertyuiop";  	//动态分配  	
	int *p1=malloc(4);  	
	int *p2=malloc(4);  	
	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); 
  • 编译通过:
    在这里插入图片描述
<think>首先,用户的问题是关于ARM32位系统中定义的变量 `int i = 1` 的地址固定性,特别是询问每次运行程序地址是否固定。用户引用了之前的回答和几个引用,所以我需要基于这些信息来回应。 回顾之前的回答: - 在ARM32系统中,变量地址固定性取决于存储类别。 - 全局变量(定义在函数外部)地址程序执行期间固定,存储在数据段(.data)。 - 静态局部变量地址也固定。 - 自动局部变量(定义在函数内部)地址不固定,存储在上,每次函数调用时可能变化。 - 地址可能受ASLR(地址空间布局随机化)影响,导致不同运行实例的地址不同。 用户现在指定了 `int i = 1`,但没有指定是全局还是局部变量。在之前的上下文中,我假设它是全局变量或其他类型。用户的问题焦点是“每次运行程序时定义的int i=1变量地址是否固定”,所以我需要澄清变量定义的位置。 引用内容: - [^1] 提到ARM Cortex-M3上的 volatile 问题,但不是很相关。 - [^2] 关于GOARCH环境变量,不直接相关。 - [^3] 讨论C语言中常量变量的存储位置:全局变量地址固定在数据段,局部变量地址上不固定。 - [^4] 关于内存区域划分,如区、区等。 关键点: - 如果 `int i = 1` 是全局变量(在函数外部定义),地址固定在数据段,但受ASLR影响,不同运行实例地址可能不同。 - 如果它是局部变量(在函数内部定义),地址不固定,每次函数调用时变化。 - 在单次程序运行时,全局变量地址固定;但多次运行程序时,地址可能随机化。 用户的问题是“每次运行程序时定义的int i=1变量地址是否固定”。所以,我需要区分: - 在单次程序运行期间(runtime):地址固定。 - 跨多次程序运行(不同实例):地址可能不固定,由于ASLR。 ASLR是Address Space Layout Randomization,用于安全目的,随机化内存布局。 在ARM32系统中,是否启用ASLR取决于操作系统。例如,在Linux ARM上,如果ASLR启用,全局变量地址每次运行会随机化。 结构化回答: 1. **澄清变量定义位置**:因为地址固定性取决于变量全局还是局部。 2. **解释全局变量情况**: - 地址固定于数据段。 - 但受ASLR影响,每次运行地址可能不同。 3. **解释局部变量情况**: - 地址每次运行不固定,甚至在每次函数调用内变化。 4. **基于引用**:引用[^3]和[^4]支持这些点。 5. **添加示例**:例如,C代码展示不同变量类型的地址行为。 6. **相关问题**:按照指令生成相关问题。 回答结构: - 先直接回答问题。 - 然后逐步解释。 - 使用中文。 - 数学表达式用$...$或$$...$$格式。 - 生成相关问题。 最终回答: - 如果 `int i = 1` 是全局变量,每次运行程序地址可能不固定(由于ASLR),但在单次运行内固定。 - 如果是局部变量,每次运行和每次调用都不固定。 用户说“定义的int i=1”,可能默认是全局变量,但最好指定。 </think>### ARM32位系统中变量 `int i = 1` 的地址固定性分析 在ARM32位系统中,变量 `int i = 1` 的地址是否固定,取决于变量的**存储类别**(全局变量局部变量)和操作系统特性(如ASLR机制)。以下是详细分析,基于您的引用和ARM32架构的内存管理机制。关键点总结: - **全局变量**(函数外定义):地址在单次程序运行内固定,但每次运行程序(不同实例)地址可能变化(受ASLR影响)[^3][^4]。 - **局部变量**(函数内定义):地址在每次运行程序时都不固定,甚至在单次运行内也可能变化(存储在上)[^3][^4]。 - **ASLR影响**:地址空间布局随机化(ASLR)是常见安全特性,可能导致全局变量地址在多次运行中随机化[^3]。 下面分步解释: #### 1. **变量定义位置决定地址固定性** - **全局变量(函数外定义)** 示例代码: ```c int i = 1; // 全局变量,定义在函数外部 int main() { printf("地址: %p\n", &i); // 输出地址 return 0; } ``` - **地址固定性**: - **单次运行内固定**:一旦程序启动,变量地址分配在数据段(`.data`),在整个运行期间不变[^3][^4]。 - **多次运行间不固定**:如果系统启用ASLR(如Linux ARM系统),每次运行程序时,OS会随机化内存布局,导致地址变化[^3][^4]。 例如: - 第一次运行地址:`0x20001000` - 第二次运行地址:`0x20003000` - **原因**:全局变量在编译时分配固定地址(由链接脚本决定),但ASLR在加载时修改基址[^3][^4]。 - **局部变量(函数内定义)** 示例代码: ```c int main() { int i = 1; // 局部变量,定义在函数内部 printf("地址: %p\n", &i); // 输出地址 return 0; } ``` - **地址固定性**: - **每次运行都不固定**:局部变量存储在(stack)内存区域。地址程序启动时随机分配(受ASLR影响),且在函数调用时动态变化[^3][^4]。 - **单次运行内也可能变化**:如果变量在循环或递归中定义,地址每次函数调用都不同[^3]。 - **原因**:是动态内存区,地址由运行时环境管理,不受编译时固定约束[^3][^4]。 #### 2. **ARM32内存映射与地址分配机制** ARM32系统中,内存划分为以下区域(引用[^3][^4]): | 内存区域 | 变量类型 | 地址固定性(单次运行) | 地址固定性(多次运行) | |----------------|--------------------|------------------------|------------------------| | **数据段(.data)** | 全局变量(`int i=1`) | 固定 ✔️ | 可能变化(受ASLR影响) | | **(stack)** | 局部变量(`int i=1`) | 不固定 ✖️ | 不固定 ✖️ | | **静态区** | `static int i=1` | 固定 ✔️ | 可能变化(受ASLR影响) | | **(heap)** | `malloc`分配 | 动态变化 ✖️ | 动态变化 ✖️ | - **ASLR机制**:在支持ASLR的系统(如Linux on ARM),全局变量地址在每次运行程序时随机偏移。例如,地址偏移量 $\Delta$ 满足 $0 \leq \Delta \leq 2^{20}$,导致绝对地址不固定[^3][^4]。 - **无ASLR场景**:在裸机ARM系统(如STM32嵌入式开发),全局变量地址完全固定,因为无OS干预[^3]。 #### 3. **验证实验** 在Ubuntu ARM32STM32开发板上运行以下代码: ```c #include <stdio.h> int global_i = 1; // 全局变量 int main() { int local_i = 1; // 局部变量 static int static_i = 1; // 静态局部变量 printf("全局变量地址: %p\n", &global_i); printf("局部变量地址: %p\n", &local_i); printf("静态变量地址: %p\n", &static_i); return 0; } ``` - **结果**: - 全局和静态变量:单次运行地址固定,多次运行地址变化(ASLR启用时)。 - 局部变量:每次运行地址都改变。 #### 4. **如何确保地址固定** - 禁用ASLR(测试用):在Linux ARM中运行 `echo 0 | sudo tee /proc/sys/kernel/randomize_va_space`。 - 链接脚本指定:在编译器(如GCC)中使用链接脚本锁定地址: ```ld SECTIONS { .my_data 0x20000000 : { *(.data) } // 强制全局变量到固定地址 } ``` 此时地址可固定在物理地址如 $0x20000000$[^4]。 ### 总结 - **若 `int i = 1` 是全局变量**: 地址在单次运行程序内固定,但每次运行程序(不同实例)地址可能不固定(受ASLR影响)。 - **若 `int i = 1` 是局部变量**: 地址在每次运行程序时都不固定。 - **关键因素**:存储类别和ASLR机制决定固定性[^3][^4]。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值