首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >指针这几个没用的判空,你做了吗?

指针这几个没用的判空,你做了吗?

作者头像
程序员的园
发布2025-05-09 12:03:43
发布2025-05-09 12:03:43
1680
举报

好像智能指针出来后,裸指针变成了洪水猛兽,在项目里都敬而远之了,导致很多裸指针的知识点也就被束之高阁,但是我个人认为对于基础知识的深入了解才能走的更远,今天我也就斗胆抛砖引玉,梳理下裸指针的几个知识点,与大家共享。

1.new 完需要判空吗

我之前写的代码、同事现在写的代码、读者群内部分同学也说,都会在new 后默认加一个指针是否为空的判断——指针为空时做一些容错处理。形如:

代码语言:javascript
复制
int* p = new int(42);  
if (p == nullptr) {
    // 处理内存分配失败的情况
}

int* p_array = new int[10];
if (p_array == nullptr) {
    // 处理内存分配失败的情况
}

但是,new完的指针真的有必要进行判空吗?为了探究这个问题,查看对应的源码(以MSVC为例),源码实现如下:

代码语言:javascript
复制
_VCRT_EXPORT_STD _NODISCARD _Ret_notnull_ _Post_writable_byte_size_(_Size) _VCRT_ALLOCATOR
void* __CRTDECL operator new(
    size_t _Size
    );

_VCRT_EXPORT_STD _NODISCARD _Ret_notnull_ _Post_writable_byte_size_(_Size) _VCRT_ALLOCATOR
void* __CRTDECL operator new[](
    size_t _Size
    );

可以看到,new 运算符的实现中加入了修饰符_Ret_notnull_,它的作用是告诉编译器,该函数返回的指针不会为空。

如果说如上所示的源码还是比较片面,但是C++标准对于new 运算符的行为,是这样描述的:

代码语言:javascript
复制
Throws an exception of a type that would match a handler of 
type std::bad_alloc on failure to allocate memory.

那么函数真的会抛出异常吗?如下的测试程序确实会抛出异常

代码语言:javascript
复制
int main() {

    //编译器对单次分配过大内存会有编译报错,
    //所以少量多次分配,避免编译报错
    for (size_t i = 0; i <( 2<<30); i++)
    {
        auto p = new int[2<<10];
    }
    return 0;
}

问题:make_shared、make_unique的返回值需要判空吗?欢迎评论区讨论

2. 禁止new 的异常

经过如上的分析可知,new 分配失败时会抛出异常,但是异常是不可控的,作为程序员我们总是希望程序是可控的,那如何禁止new 抛出异常呢? C++标准库提供了std::nothrow 关键字,它可以关闭new 分配失败时抛出异常的机制,转而返回空指针。可按如下的方式使用该关键字:

代码语言:javascript
复制
#include <new>
int* p = new(std::nothrow) int(42);
if (p == nullptr) {
    // 安全处理分配失败的情况
}

在MSVC中,std::nothrow版本的new操作符实现如下:

代码语言:javascript
复制
_VCRT_EXPORT_STD _NODISCARD _Ret_maybenull_ _Success_(return != NULL) _Post_writable_byte_size_(_Size) _VCRT_ALLOCATOR
void* __CRTDECL operator new(
    size_t _Size,
    ::std::nothrow_t const&
    ) noexcept;

_VCRT_EXPORT_STD _NODISCARD _Ret_maybenull_ _Success_(return != NULL) _Post_writable_byte_size_(_Size) _VCRT_ALLOCATOR
void* __CRTDECL operator new[](
    size_t _Size,
    ::std::nothrow_t const&
    ) noexcept;

可以看到,std::nothrow版本的new操作符实现中加入了修饰符_Ret_maybenull_,它的作用是告诉编译器,该函数返回的指针可能为空。

之前抛出异常的代码使用std::nothrow后,就不会抛出异常了,执行了返回值为空的逻辑,保证了程序的健壮性。

代码语言:javascript
复制
int main() {

    for (size_t i = 0; i <( 2<<30); i++)
    {
        auto p = new(std::nothrow) int[2<<10];
        if (p == nullptr) {
            printf("new failed\n");
        }
    }
    return 0;
}
//输出:new failed

3. 指定内存上构建对象

如上讲述的new操作符,都是通过operator new函数分配内存,然后调用对象的构造函数。一旦涉及到内存分配动作,便有可能出现内存分配失败的情况,无论是抛出异常还是返回空指针都不能构建出对象,都会影响预定的实现逻辑。为此,C++标准提供对应的解决办法,那就是使用placement new——在指定的内存上分配对象,而不是通过operator new函数分配内存。 具体的书写方式如下:

代码语言:javascript
复制

char buffer[sizeof(int)];
int* p = new (buffer) int(123);  // 在指定内存上构造对象
//some code
p->~int();  // 显式析构

注意:

  • 预分配的内存必须足够大,以容纳对象的大小。在两个字节的内存上分配一个int对象是不合法的。
  • new操作符不会分配内存,只负责调用构造函数。所以指针用完后,不能用delete操作符销毁对象,而是应该手动调用析构函数。
  • 预分配的内存需要手动释放,否则会导致内存泄漏。
  • placement new 是构建内存池、自定义容器、或管理对象生命周期粒度精细化的重要技术,但也带来了较高的责任。其使用场景需极度谨慎,并确保对象构造与析构始终成对出现。

4. delete 指针需要判空吗

new 完判空一样,好像delete指针时,大家也会默认加一个判空,避免空指针的问题,但是这真的有必要吗? 其实也是完全没有必要的,C++ 明确规定,对空指针使用 delete 操作是安全的:

代码语言:javascript
复制
int* p = nullptr;
delete p;  // 无操作,不会导致崩溃
p=nullptr; // 一定要置空

我们知道了delete空指针是安全的,所以需要注意,delete完指针后,一定要将指针制空,避免出现double free的问题——多次删除同一非空指针(悬空指针)是有问题的

问题:空的shared_ptr、unique_ptr在reset前需要判空吗?

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-05-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员的园 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.new 完需要判空吗
  • 2. 禁止new 的异常
  • 3. 指定内存上构建对象
  • 4. delete 指针需要判空吗
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档