详解内存空间
0. 概述
一个C/C++ 程序,编译之后,形成的程序,在执行期间,内存中不仅存在一块区域用于存放代码,还有一些其他的区域用于使用,本节会详解C/C++内部所使用的内存地址空间,关于各内存的作用、位置做一个整体概述。
1. C++ 内存布局
操作系统的内存布局可大致分为两块:内核态和用户态,此处的内核态和用户态,不同于操作系统在执行时所进入的内核态和用户态,此处专指内存。二者有类似之处,例如:只有操作系统处于内核态时才可以访问内核态内存,因此二者容易产生混淆。
仅需了解,此处指代:内存内核态 和 内存用户态。
内存内核态暂且不去深究,内存用户态在C++程序执行时,会划分成以下内容:
【栈、堆、全局/静态区、文字常量区、程序代码区】(地址从高到低)
2. 内存分区
3. 栈区
栈:用于存放局部变量、函数参数、函数调用返回值、函数调用返回地址等数据,程序从main 主函数开始运行,到主函数运行结束,在此之间调用其他函数,可看作将其他函数进行压栈的一个过程,在栈区内的变量、地址等,当该函数执行结束后,其中的资源由系统自动释放。如main 函数中调用 func 函数,func 函数内生成一个数据,将其作为返回值进行返回。main 函数中使用变量进行接收,打印,可以看出打印值不同于 func 函数中的值(未定义行为),当func 返回后,该函数出栈,其中的资源被系统释放。
示例如下:
char *func() {
char buf[] = "hello world";
return buf;
}
int main(int argc, char *argv[]) {
char *buf = func();
cout << "buf = " << buf << endl;
return 0;
}
另外值得注意的一点是,栈区的使用,操作方式类似于数据结构中的栈,即在使用时,后进先出,函数调用机制使用栈区完成,大量频繁的调用栈区,务必会造成时间资源的消耗,导致程序的执行效率下降。C 语言解决方法是使用宏代替短小频繁的函数体,C++ 语言的解决方法是使用内联函数进行替换,还需提高编程技巧。
机器在栈上申请的空间有限,因此在栈上使用的内存需要注意大小。
4. 堆区
堆位于栈区下,全局/静态区上,栈区向下生长,堆区向上生长,因此日常提到堆栈,是因为这两者的内存空间十分接近。
堆区是程序员可手动分配的内存空间,特点是需要手动申请和手动释放,在C语言中使用 malloc 和 free 进行申请和释放。C++中则使用 new 和 delete 进行申请和释放,此处不再赘述 malloc/free 和 new/delete 的区别,若感兴趣请自行查询相关资料。
堆区申请的数据在程序执行阶段不会被系统所自动释放,因此C和C++程序员需要注意内存大小,内存释放问题。(程序结束时会被操作系统回收)
需要注意:
1.申请堆内存之后,立刻验证内存空间是否申请成功。
2.申请行为必须对应释放行为,有申请内存,则必有释放行为,因此写 malloc 之后立刻写上free语句。new/delete 同理。
3.free 和 delete 虽然传入的是指针,但释放的确是内存空间,因此需要将该指针置为null,防止野指针问题。
另外,堆的运行速度、效率不及栈,且存在内存碎片问题。 使用 malloc 和 free 时,需要添加头文件。
5. 全局/静态区
全局区、静态区是一片区域,原因在于全局变量和静态变量的内存地址都位于这片空间下。该空间位于堆下,低地址。
定义一个变量时,需要及时进行初始化,变量被定义 static 之后,只有当前模块可见。当一个局部变量被定义为 static时,虽然该局部变量的可见范围仍然属于该函数,但该局部变量的生命周期已经延长到了程序生存期。
6. 文字常量区(只读)
文字常量区用于定义常量字符串,程序结束后由系统进行释放,不可进行写操作。
const char* 定义文字常量后,直接使用字符串,二者处于的地址相同,都位于文字常量区。
#include <func.h>
#include <iostream>
using std::cout;
using std::endl;
int main(int argc, char *argv[]) {
const char *str = "Hello world";
printf("str address = %p\n", str);
printf("hw address = %p\n",&"Hello world");
return 0;
// str address = 0x561f4d567004
// hw address = 0x561f4d567004
}
局部的常量字符串放在栈或者文字常量区,视编译器而定。
7. 程序代码区(只读)
顾名思义,存放程序代码的位置,函数也是存放在某个内存地址的,比如,可以打印看一下main函数的运行地址。
#include <iostream>
using std::cout;
using std::endl;
int main(int argc, char *argv[]) {
printf("main address = %p\n", &main);
return 0;
}
参考:https://www.cnblogs.com/songdanzju/p/7422507.html