C++内存空间详细解读

发布时间:2023年12月22日

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

我们都知道C++和C语言一样都是基于堆栈结构设计的语言,Java虽然有虚拟机而且自动回收内存,他也是基于堆栈式结构设计的语言。当然,我们今天主要讨论C++的内存空间类型,只不过首先要了解它的语言结构。


一、内存空间类型

C++中有3种类型的内存空间,接下来我们逐个解答。

1.自动内存(automatic storage)

除非显式声明为static,在函数中定义的变量(包括函数的参数),存储在自动内存中(即,中)。当函数被调用时,会为其分配自动内存空间,当它返回时,这部分空间被释放

示例:

#include <iostream>

void test(int a){
    int b = 0;
}

int main() {
    std::cout << "Hello, World!" << std::endl;
    return 0;
}

a和b都是自动内存,当test函数执行完毕,a和b的空间被系统自动回收,不用手动回收

2.静态内存(static storage)

全局变量和名字空间中声明的变量存储在静态内存空间中,在函数和类中声明的变量,如果显式声明为static,也存储在静态内存中。连接器在程序开始执行之前分配静态内存空间。

示例:

#include <iostream>

int a = 0;

namespace WebCore {
    int a = 0;

    class Demo {
    public:
        static int a;
    };

    int Demo::a = 0;

    void test() {
        static int a = 0;
    }
}


int main() {
    std::cout << "Hello, World!" << std::endl;
}

上面所有的变量a都在静态内存里。

3.自由内存(free storage)

示例:

#include <iostream>

int *a = new int{1};

namespace WebCore {
    int *a = new int{1};
}

void test() {
    static int *a = new int{1};
    delete a;//这个写法比较尴尬,出了这个作用域就访问不到a了,要不然就把指针引出去
}

class Demo {
public:
    static int *a;
};

int *Demo::a = new int{1};//类内声明,类外定义

int main() {
    std::cout << "Hello, World!" << std::endl;
    std::cout << *::a << std::endl;
    test();
    std::cout << *WebCore::a << std::endl;
    std::cout << *Demo::a << std::endl;
    delete WebCore::a;
    delete Demo::a;
    delete ::a;
    return 0;
}

上面的所有变量a都在自由存储里,使用完了必须delete,要不然会内存溢出。上面的写法有几个地方不合理,但是这只是演示,不要太较真。

二、很重要

1.程序开始执行之前

上面提到了程序开始执行之前,很多人可能不能很好理解这个概念。如果换个说法可能就容易接受了。我们都知道任何C++程序都需要定义main函数,这是程序的起点函数,也就是我们程序执行的第一个函数。就是说静态内存的分配在main函数执行之前就完成了,当你在main函数里面使用静态变量的时候是已经初始化好的,这下容易理解了吧。

2.堆内存扩展

我们都知道C++是C语言的扩展,当然是很成功的扩展。C++保留了很多(不是全部)C语言的特性,让这两种语言混编几乎无任何阻碍(不支持的特性我用不到)。实际上,C语言malloccalloc的内存也是堆内存,当然也需要free释放掉

3.手动回收和自动回收

在C++中我们所说的手动回收内存是值堆内存,当然自动回收的。但请记住,C++依然是一门手动回收内存的语言,不能因为栈自动回收你就认为它是自动回收内存!我们说Java是一门自动回收内存的语言是因为Java的堆内存也是自动回收的,而负责这件事情的就是垃圾回收器,这个超出了这篇文章,感兴趣的请自行查阅。

4.C++垃圾回收接口

C++设计之初是考虑过自动垃圾回收的,很多机构也基于这个接口实现了自己的垃圾回收器。但是,作为一款定位手动回收内存的语言来说,这还是存在很多的挑战,最典型的例子就是数据转换的问题。我们都知道reinterpret_cast是完成两个不相干的数据格式之间的转换,这算是C++的一个魔幻特性了,但是不要小瞧它,有时候很有用喔!

考虑下面的代码:

#include <iostream>


int main() {
    std::cout << "Hello, World!" << std::endl;
    int *p = new int{1};
    std::cout << *p << std::endl;
    long x = reinterpret_cast<long>(p);
    p = nullptr;
    //垃圾回收器时间
    p = reinterpret_cast<int *>(x);
    *p = 10;//这个时候p还在吗?
    std::cout << *p << std::endl;
    delete p;
}

正常情况下应该打印1和10。但是,如果p = nullptr之后垃圾回收器回收了p原指向的内存,就会出现后面转换回来内存无效的情况。

有些人可能要问了,我不这么做不就行了吗?其实像这种情况还有很多,而且reinterpret_cast有时候是一个非常有用的工具。比如:OpenCV的Java代码调用C++底层就用到了这个特性,Mat的内存指针被转换成long型传回Java层,在需要使用的时候再传到C++层转换回来。这种转换几乎没有效率上的损耗,要不然Java和C++之间的内存数据转换将是一个及其头疼的问题!

说到底,终究是谁也不能给出一个完美的解决方案,有利也有弊。


总结

1、用好三种内存就足够了

文章来源:https://blog.csdn.net/jiexijihe945/article/details/135153980
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。