【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++】智能指针(一)

相关推荐
江畔柳前堤15 分钟前
前台·中台·后台:2026年AI原生时代的架构全景图
开发语言·人工智能·算法·机器学习·架构·scala·ai-native
Figo_Cheung25 分钟前
Figo基于RNC理论的宇宙演化第七纪元猜想
开发语言·php
星河单片机1 小时前
STM32驱动WaterSensor水位传感器+OLED显示完整原理与实验
stm32·单片机·嵌入式硬件
会周易的程序员1 小时前
5Draft(五帝)测试报告
服务器·c++·分布式·raft·共识算法·共识·算力服务器
UIU1141 小时前
scanf与cout的误区:探究其内部的机制
c++·学习·c#·scanf
闻缺陷则喜何志丹1 小时前
【栈 运算系统】P3719 [AHOI2017初中组] rexp|普及+
c++·算法··洛谷·运算系统
泡海椒1 小时前
JQuick-Curl 二次开发:自定义解析器、扩展点开发指南,如何把框架能力真正变成团队资产
java·开发语言·okhttp·maven
子文雨2 小时前
基于 uC3845 与 ATtiny13 双芯片架构的 8.4V/2.5Ah 锂电池智能充电器设计
单片机·嵌入式硬件·算法
bnmoel2 小时前
C++ 基础入门篇(一):命名空间,输入&输出,缺省参数,函数重载
c++·函数重载·语法·命名空间·缺省参数
WWJA王文举2 小时前
FreeRTOS事件组和任务通知详解:为什么任务通知比队列更轻量?
开发语言·php