【C++】C++程序的四个区和智能指针的实现

这篇文章介绍下 C++ 程序的四个区,以及一个智能指针的简单实现。

起因

最近在公司审查代码的时候,coverity 对以下代码:

cpp 复制代码
T fun()
{
	Obj obj;
	//代码逻辑
}

报出了 obj 占用空间过大,有可能栈溢出的问题。

以前从来没有考虑过C++的代码存放位置的问题,这次顺便就学习了下。

C++四区

在执行一个C/C++语言程序时,此程序将拥有唯一的"内存四区"------栈区、堆区、全局区、代码区。不过这里的栈和堆并不是数据结构里的栈和堆。

栈区(stack)

存放各种临时变量,包括函数内部声明的临时变量,函数调用的参数以及函数本身调用产生的入栈出栈操作等。

堆区

存放程序员自己分配的内存,如果程序员自己分配了,没有释放,那程序结束以后可能会被操作系统释放?

静态区

存放静态变量,包括全局变量,static 修饰的变量。C语言时期,静态区的变量又分为初始化的和未初始化的,两者分别在不同的位置。C++取消了这个设计。

文本区

存放常量,以及代码本身。

我们用一个例子来展示这几个区:

cpp 复制代码
int global_a = 0;
char global_c;
int global_b;

我们声明三个全局变量,分别打印出他们的地址(防止入栈出栈操作干扰,这里用宏定义):

cpp 复制代码
#define PRINT_ADDR(val) cout << (int)&(val) << endl

打印出来的结果如下:

bash 复制代码
3264544
3264548
3264552

int 类型占用 4 个字节,char 类型占用 1 个字节,这里占用 4 个可能是由于内存对齐的问题。

接下来我们在 main 函数内部声明三个局部变量:

cpp 复制代码
    int temp_c;
    double *temp_d = new double(1.0);
    int temp_e = 3;

打印的结果为:

bash 复制代码
10942212
10942208
10942204

与上面变量的内存地址明显有很大差别,另外注意这里的 double 类型虽然占用 8 字节,但是我们声明的是一个指针,无关类型,是固定的 4 字节。同样,new 出来的内存在堆上,将堆上的地址返回存储在 temp_d中。

最后是两个静态变量和一个常量:

cpp 复制代码
    static int static_e = 2;
    static int static_f = 4;
    string temp_f = "f";

地址为:

cpp 复制代码
3207168
3207172
3215360

本次出问题的地方就在栈区。因此将临时变量改为从堆上 new/delete 内存即可。但是需要注意的是,new/delete 的操作会在运行期间动态分配内存,而入栈与出栈操作仅在编译期间就可以完成,这相当于把编译的内存压力转给了运行期,并不是一个合理的做法。

智能指针的简易实现

使用 new/delete 的组合容易出现忘记 delete 的问题,事实上,我们可以使用一个工具类"包裹"原始的类,在构造时调用 new,在析构时调用 delete,我给出一个简单的实现如下:

cpp 复制代码
template <typename Obj>
struct AutoObj
{
    AutoObj(Obj* obj)
    {
        actualObj = obj;
    }

    ~AutoObj()
    {
        if (actualObj != nullptr)
        {
            delete actualObj;
        }
    }

private:
    Obj *actualObj = nullptr;
};

可以编写一个类,测试一下效果:

cpp 复制代码
struct Student
{
    Student(int age):age(age)
    {
        cout << "Student construct" << endl;
    }

    ~Student()
    {
        cout << "Student delete" << endl;
    }
private:
    int age;
};
cpp 复制代码
AutoObj<Student> stu{new Student(10)};

看打印发现析构函数已经被正确地调用了:

bash 复制代码
Student construct
Student delete

这个简易的智能指针的实现实在过于简易了,无法处理一些异常情况,需要根据具体情况去实现其他比如复制构造函数之类的接口。

当然,你也可以直接用智能指针,见【C++】智能指针(一)

相关推荐
青莳吖2 分钟前
Java通过Map实现与SQL中的group by相同的逻辑
java·开发语言·sql
Buleall10 分钟前
期末考学C
java·开发语言
重生之绝世牛码12 分钟前
Java设计模式 —— 【结构型模式】外观模式详解
java·大数据·开发语言·设计模式·设计原则·外观模式
小蜗牛慢慢爬行18 分钟前
有关异步场景的 10 大 Spring Boot 面试问题
java·开发语言·网络·spring boot·后端·spring·面试
MARIN_shen23 分钟前
Marin说PCB之POC电路layout设计仿真案例---06
网络·单片机·嵌入式硬件·硬件工程·pcb工艺
Algorithm157628 分钟前
云原生相关的 Go 语言工程师技术路线(含博客网址导航)
开发语言·云原生·golang
shinelord明37 分钟前
【再谈设计模式】享元模式~对象共享的优化妙手
开发语言·数据结构·算法·设计模式·软件工程
Asa31940 分钟前
STM32-按键扫描配置
stm32·单片机·嵌入式硬件
Monly2143 分钟前
Java(若依):修改Tomcat的版本
java·开发语言·tomcat
boligongzhu44 分钟前
DALSA工业相机SDK二次开发(图像采集及保存)C#版
开发语言·c#·dalsa