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

相关推荐
_.Switch9 分钟前
高级Python自动化运维:容器安全与网络策略的深度解析
运维·网络·python·安全·自动化·devops
JokerSZ.14 分钟前
【基于LSM的ELF文件安全模块设计】参考
运维·网络·安全
老猿讲编程15 分钟前
一个例子来说明Ada语言的实时性支持
开发语言·ada
SafePloy安策16 分钟前
软件加密与授权管理:构建安全高效的软件使用体系
安全
芯盾时代1 小时前
数字身份发展趋势前瞻:身份韧性与安全
运维·安全·网络安全·密码学·信息与通信
UestcXiye1 小时前
《TCP/IP网络编程》学习笔记 | Chapter 3:地址族与数据序列
c++·计算机网络·ip·tcp
Chrikk1 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*1 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue1 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang