其实在之前也写过类似的,不过那个时候只是一直半解,今天我就抽时间整合一下最近的理解。
在这里插一下栈中数组地址增长方向
栈的属性和数组地址的增长方向_栈区数组增长方向-CSDN博客
结合一下ARM Cortex M3 系列的架构来进行分析。
一般我们在使用keil这个软件进行编译时可以看到在编译成功后
这里红色框框里的其实就是
Code为程序代码部分 = 程序代码区(code)
RO-data 表示 程序定义的常量 = 文字常量区
RW-data 表示 已初始化的全局变量 = 栈区(stack)堆区(heap)全局区(静态区)(static)
ZI-data 表示 未初始化的全局变量
单片机内存被总分为flash(rom)和sram(ram),flash里面的数据掉电可保存,sram中的数据掉电就丢失,sram的执行速度要快于flash,flash容量大于sram。一般下载的时候都是下载在FLASH里。
接下来我们再看一下下面这个图
这就是在代码中变量的存放位置。其实总共就分五个区,分别为栈区、堆区、全局区、代码区、常量区。
1.栈区:由编译器自动分配释放,存放函数的参数值、局部变量的值等。当调用函数的时候函数中定义的变量会被加到栈中,当函数离开的时候,被添加的变量会从栈中移除,栈在最高的地址上,所以添加的变量地址会逐渐变小,里面的内容可读可写。
2.堆区:一般由程序员分配和释放内存空间,例如我们在使用链表申请节点内存时,malloc函数申请的内存就是堆区的内存空间,使用完后要用free函数释放空间。里面的内容可读可写。
3.全局区/静态区:全局变量和静态变量就存放在这里,初始化且不为0的全局变量和静态局部变量在一块区域,这块区域我们称之为.data区。未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,这块区域我们称之为.bss区。程序结束后由系统释放,是由系统管理的可读可写的内存。
4.常量区:该内存区存放的常量,数字常量、字符常量、字符串常量等,不允许修改,所以常量区的内存是只读的。
5.代码区:存放程序的二进制代码,代码区的内容也是只读型。
这两个图的对应关系为
放个代码辅助理解
#include <stdio.h>
const int read_only_variable = 2000;
int data = 500;
void my_function(void)
{
int x = 200;
char *str = "string";
}
在上述代码中
read_only_variable 是一个用 const 修饰的全局变量,它是只读的,存放在 flash 中的只读数据区域,编译器会给 read_only_variable 分配一个地址,并将 2000 这个数据存放到常量区。
data 这个变量将存放到 RAM 中的RW区域中 (后面将会进行详细讲解),但是 data 后面的初始值 500 将会被存放到数据复制区域中, 也就是上图中从下往上的第三个区域。
在 my_function 中的变量 x 将会被存放到 RAM 中的堆栈中,将 x 赋值为 200 ,200 将被存储到 flash 里的 Text 中的常量区 (Literal Valu) 中。
str 是一个 char 型的指针变量,它指向的是字符串第一个字符存放的位置。
然而对于字符串 string 来讲,它是存放在Text常量区的,所以指针变量指向这个区域的一个地址,但是因为它终归中局部变量,它指向 Flash 的一个地址,但是其本身还是存放于 RAM 中的堆栈上的。
再举一个例子
#include <stdio.h>
#include <stdlib.h>
int data_var = 500;
int bss_var0;
int bss_var1 = 0;
static int static_var;
void my_function(void)
{
static int static_var1 = 0;
int stack = 0;
char *buffer;
const int value = 1;
buffer = malloc(10);
}
上述变量的命名已经很清楚地表明了变量处于 RAM 中的哪一个段,datavar 是已经初始化的全局变量,存放在 RAM 的 data 区,bssvar0 和 bssvar1是未初始化和初始化为0的全局变量,他们都存放于 RAM 中的 bss段,由 static 修饰的staticvar 和 static_var1 都存放于 bss段,区别只在于两个变量的作用域不同。stack 是在函数内部定义的局部变量,其存放于 RAM 的栈区域,用 const 修饰的局部变量 value ,虽然他是只读的,但是它是存储于 RAM 中的栈中的,这里也说明一点,并不是所有用 const 修饰的变量都是存放于只读变量区的(const全局变量存储在只读数据段,编译期最初将其保存在符号表中,第一次使用时为其分配内存,在程序结束时释放,例如:val_c;const局部变量存储在栈中,代码块结束时释放,例如:val_j。)。buffer指针变量用 malloc 函数申请了 10 字节的内存空间,那这10字节的内存空间位于堆中。
函数指针参数
当调用 set
函数时,将一个常量整数的地址传递给函数参数 A
,这个常量整数可以存储在不同位置。以下是一些示例:
-
常量整数存储在栈上:
#include <stdio.h> int set(const int *A) { printf("Value at address: %d\n", *A); return 0; } int main() { const int num = 10; // 常量整数存储在栈上 set(&num); // 将常量整数的地址传递给 set 函数 return 0; }
-
常量整数存储在全局数据段:
#include <stdio.h> const int globalNum = 20; // 全局常量整数 int set(const int *A) { printf("Value at address: %d\n", *A); return 0; } int main() { set(&globalNum); // 将全局常量整数的地址传递给 set 函数 return 0; }
3、常量整数存储在堆上:
#include <stdio.h>
#include <stdlib.h>
int set(const int *A) {
printf("Value at address: %d\n", *A);
return 0;
}
int main() {
const int *ptr = (int*)malloc(sizeof(int)); // 分配内存存储常量整数
*ptr = 30; // 设置常量整数的值
set(ptr); // 将常量整数的地址传递给 set 函数
free(ptr); // 释放动态分配的内存
return 0;
}
对于函数中被 const
修饰的参数,其存放位置取决于参数的类型和传递方式。一般来说,const
修饰的参数可能存放在以下几个位置:
-
栈上:如果参数是通过值传递(pass by value)或者以引用(指针)方式传递,并且函数内部并不修改该参数的值,那么这个参数通常会存放在栈上,即在函数调用时被压入栈中。
-
只读数据段:对于全局常量或静态常量作为参数并且被声明为
const
的情况,这些常量参数可能会存放在程序的只读数据段中,以确保在函数内部不会被修改。 -
堆上:如果参数是指向动态分配内存的指针,并且被声明为
const
,那么这个指针可能指向堆上存储的数据,因为动态分配的内存是在堆上分配的。