C/C++程序内存区域划分
直接上图:
在这里插入图片描述
注:以下的说明均已VS2019为例
栈区(stack)
在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元会自动释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存空间有限。栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
在栈空间存储数据时地址的变化
栈空间遵循“先入后出”原则。
栈区的数据是从高地址向低地址存储的,最新压入栈的数据位于栈底(高地址),而最早压入栈的数据位于栈顶(低地址)。
我们以一个具体的例子来进行说明:
#include<stdio.h> #include<stdlib.h> int main() { char a = 10; char b = 10; printf("%p\n", &a); printf("%p\n", &b); return 0; }
output
008FFA3B 008FFA2F
- 局部变量a先于变量b存入栈区,因此a的地址要高于b的地址
在栈空间读取数据时地址的变化
当读取栈区的数据时,是从低地址向高地址访问,也就是从栈顶向栈底读取数据。
首先我们要明白,在VS2019中,栈区中的数据是以小端字节序存储的,还不清楚的小伙伴看这里👉整数在内存中的存储
我们以一个具体的例子来进行说明:
#include<stdio.h> #include<stdlib.h> int main() { int num = 0x11223344; char* pc = # printf("%x\n", *pc); return 0; }
output
44
- int型num占4个字节,由于采用小端存储,低位数据
44
要放在低地址:
- 而由于栈区的数据是由低地址向高地址访问,因此指针pc指向的应该是变量num的低位字节,管理一个字节,因此打印
44
数组在栈区的存储
#include<stdio.h> #include<stdlib.h> int main() { int num1[5] = { 0 }; for (int i = 0; i < 5; i++) printf("%p\n", &num1[i]); printf("\n"); int num2[5] = { 0 }; for (int i = 0; i < 5; i++) printf("%p\n", &num2[i]); return 0; }
output:
0113FE08 0113FE0C 0113FE10 0113FE14 0113FE18 0113FDE0 0113FDE4 0113FDE8 0113FDEC 0113FDF0
可以画出示意图:
可以得出结论:
随着下标的增长,数组元素的地址逐渐加大
堆区(heap)
堆区一般存储的是动态内存,如由malloc,calloc,realloc
动态开辟的空间。这些空间一般由程序员分配释放,若不释放,程序结束时可能由OS回收,但若程序没有结束,则会造成内存泄漏,因此动态开辟的空间一定要记得free释放。内存分配的方式类似于链表
在堆区存储数据时地址的变化
通常情况下,堆区的地址分配是从低地址到高地址进行的,即在堆区中分配的内存空间的地址是逐渐增大的。这是因为堆区是通过动态内存分配来实现的,通常使用链表或类似的数据结构来管理已分配和未分配的内存块。新分配的内存块会被添加到链表的末尾,因此地址是递增的。
例如:
#include<stdio.h> #include<stdlib.h> int main() { int* arr1 = (int*)malloc(sizeof(int) * 5); if (NULL == arr1) { perror("malloc"); return 1; } int* arr2 = (int*)malloc(sizeof(int) * 5); if (NULL == arr2) { perror("malloc"); return 1; } printf("%p\n",arr1); printf("%p\n",arr2); free(arr1); free(arr2); arr1 = NULL; arr2 = NULL; return 0; }
output
00ED1E28 00ED1E68
可以看到后存入的数据地址更大
在堆区读取数据时地址的变化
在堆区中读取数据时,并没有固定的规定要求从高地址到低地址读取。可以通过指针来访问堆区中的数据,可以根据需要进行读取和修改操作,而不是受地址增长方向的限制。
数组在堆区的存储
#include<stdio.h> #include<stdlib.h> int main() { int* num1 = (int*)malloc(sizeof(int) * 5); if (NULL == num1) { perror("malloc"); return 1; } for (int i = 0; i < 5; i++) printf("%p\n", &num1[i]); printf("\n"); int* num2 = (int*)malloc(sizeof(int) * 5); if (NULL == num2) { perror("malloc"); return 1; } for (int i = 0; i < 5; i++) printf("%p\n", &num2[i]); free(num1); free(num2); num1 = NULL; num2 = NULL; return 0; }
output
00ED1E28 00ED1E2C 00ED1E30 00ED1E34 00ED1E38 00ED1E68 00ED1E6C 00ED1E70 00ED1E74 00ED1E78
可以画出示意图(注意比较和栈区的区别):
数据段(静态区)
(static)存放全局变量、静态数据。程序结束后(不是函数结束)由系统释放
代码段
存放函数体(类成员函数和全局函数)的二进制代码以及只读常量。
关于数据的读取
在计算机系统中,数据的存储和读取通常是以字节为单位进行的。对于单个字节的数据,无论是在堆区、栈空间还是其他存储区域,数据的存储和读取都是从低位(低地址)向高位(高地址)进行的。
在C语言中,数据类型的存储和读取也遵循这个原则。例如,对于整型数据类型(如int、long等),它们通常占用多个字节的内存空间,其中每个字节都按照从低位到高位的顺序存储数据。
当我们读取一个整型变量时,计算机会从最低有效字节开始,按照地址逐字节读取数据,然后根据数据类型的大小将其合并为一个完整的值。这种方式被称为"小端字节序"(Little-Endian),它是目前主流的处理器架构所采用的方式。
需要注意的是,在特定的硬件平台或编译器中,也可能存在"大端字节序"(Big-Endian)的方式。在大端字节序中,数据的存储和读取是从高位到低位进行的。但是,大端字节序相对较少见,小端字节序是C语言中的常见规范。
综上所述,一般情况下,在计算机系统中,数据的存储和读取是从低位向高位进行的,即从低地址到高地址进行。这是C语言和许多常见的计算机系统所采用的数据存储方式。