再论空指针检测问题

news/2025/5/23 7:13:49

某些C/C++编程的书中,曾经提到如何判断指针是否为空的问题.很显然,if (p == NULL), if (p == 0) if(p),都能够完成这一任务,差别在于可读性方面.我们分别加以讨论.

 

1. if (p == NULL)

相当多的文章建议采用,他们中的部分人甚至认为,其他做法都是错误的.这个形式一个变种是 if (NULL == p),虽然略显怪异,但是,万一我们误将==写成了=,编译器通常都会给出编译错误,从而防止键入的错误.拥护这一方案的人认为,NULL可以明确地表达出p 是一个指针,而不是整数或其他什么东西.有助于我们理解代码.然而,反对者也众多,包括C++之父B.S.反对的主要根据是,NULL并不是语言的一部分,通常,它是由一个宏定义而来的:

#define NULL ((void*)0)

一般来说,NULL其实可以工作的很好,但是,宏的出身,让它不让人喜欢.另外,它也确实有些现实的问题,典型的,成员指针的问题.假设有类型A,

typedef void (A::*FUNT)();

现在,FUNT定义成一个成员函数指针类型了.假设有FUNT fun = ...

对于if (fun == NULL)将会导致编译失败.原因在于FUNTvoid*之间不存在类型转换.不仅仅成员函数存在这个问题,普通函数指针也可能存在这个问题.

那么,我们改变NULL的定义呢?尝试这样定义:

#define NULL (0)

或者

#define NULL 0

这也可能存在问题.NULL这个名字意味着自己是个指针,只允许指针和NULL比较,然而事实上却并非如此.这有欺骗的嫌疑.许多人认为,欺骗不可接受,暴露问题比欺骗要好得多.这种欺骗导致编译器无法提供恰当的帮助.

 

2. if (p == 0)

包括B.S在内的许多人也推荐这种方式----尽管他们大多认为这个方案并不完美.首先,语言内部对于常量0有特殊的处理,因此,第一种选择中可能遇到的和函数或成员函数指针比较失败的问题可以解决.另一方面,它没有强调自己必须和指针比较,因此,也不存在欺骗.而且,因为这种方式受语言支持,每个C++ 程序员都会熟悉这一方法,而不会觉得难以理解.并且,一个新的语言改进正在进行,会为语言提供一个nullptr的关键字,专门用来和各种指针比较.但目前的语言实现基本上还不支持这一特性.

 

3if (p)

许多C++的铁杆或资深用户对前两个方案嗤之以鼻.他们认为这个方案正确,简单,有效. 另外,这一方案可以有效地应用到所谓smart_ptr上去.12方案往往会带来潜在的不安全因素.假设,我们现在有一个smart_ptr,为了支持第一种语法,我们需要为之定义一个全局的比较函数.但是,这也意味着要提供一系列长相难看(参数类型不对称)的重载,而且,需要花费很多精力.并且,难以提供一个完全一致的实现.

 

我们看一下,为了支持smart_ptr,不同方案分别需要做些什么.

为方案1,我们要定义:

bool operatro ==( const type& L, void* null);

bool operatro ==( void* null, const type& R);

同样,还需要重载operator !=;

这里的问题是,smart_ptr我们本来不指望和任何void指针比较相等的,但是现在不得不允许了.也就是说,我们可以和一个非NULLvoid指针比较了.而我们的本意是:查询这个指针是不是空.

方案2存在同样的问题,而且更严重.我们可能和任意整数比较了.而对于普通的指针,和非0常量比较会编译失败的.

另一个手段,借助于隐式转换,但是那更加不安全.

在第三个方案中,我们通常会重载operator ! operatorbool.虽然重载operator bool也是危险的,但是现在技术上可以绕过这一点,就是重载一个unspecified_boolean的办法,类似实现如下:

template

    classunspecified_boolean_imp

    {

        structTester{    void dummy(){}        };

        typedefvoid (Tester::*unspecified_boolean_type)();  

    public:

        operatorunspecified_boolean_type() const THROW0() {

           return static_cast(*this).operator!() ? 0 : &Tester::dummy;

        }

    protected:

       unspecified_boolean_imp(){}

       ~unspecified_boolean_imp(){};

    };

 

这样,我们可以安全使用if (p)的语法而不必担心被误用.

第三个方案常见于GP代码.然而,拥护1,2方案的人往往反对,认为3不利于阅读,过于技巧化.而3的拥护者认为,3明显是一种更务实有效的做法,可以使得实现简单明了,而且牢靠.

那么回顾一下我们的意图:查询指针是不是空.那么为什么不提供一个查询的函数呢?把差异封装掉不是很好?

于是作下述尝试:

if (is_nullptr(p));

嗯,我觉得这样的代码比if (p == NULL)更加直白.如果is_nullptr定义成宏,可以这样提供:

#define IS_NULLPTR(x) ((x) == 0)

可是,我不喜欢宏,而且,我觉得,宏能够帮我做的事情太少,例如,如果p是一个int,编译器无声无息地成功.我觉得,很明显地is_nullptr对于非指针类型应该给个编译错误.事情落到了template头上:

template bool is_nullptr(T* p){

    return !p;

}

现在,我可以安心地写if (is_nullptr(p))了,如果pint类型,会编译出错.但是,还有两个问题没有解决.一个是smart_ptr,另一个是成员函数指针. smart_ptr其实很简单,重载就是了

template

bool is_nullptr(const smart_ptr& p){ return !p;}

借助于traits技术来维护concept,事实上,可以做得更好,我们只需要一行额外代码,在smart_ptr的定义中加入:

enum { is_pointer = 1};

就足矣.即使我们无法修改smart_ptr,我们仍然有后路,不是吗?如果第三方的smart_ptr不支持if (p)这样的语法,但是,通过重载is_nullptr,我们可以为所有的可能提供唯一的使用形式.我喜欢这种简单性.

函数指针不需要我们做特别的处理就已经支持了.现在处理成员函数指针:

template

bool is_nullptr( T U::* x)

{

    return !x;

}

幸运的是,它可以工作.无论是针对成员函数还是成员数据.而且,也无视函数的参数列表.曾经在实现变长template参数地狱中幸存的人们,是不是觉得世界太不公平了?^_^

对于函数,这里的T不仅仅是返回值,实际上是一个函数定义.不过,我不确定这是否确实符合ISO标准.不过没关系,大不了重回地狱,I will be back!

你可以批评我的巴洛克倾向,我要开始享受静态类型安全的is_nullptr了.

文章来源:https://blog.csdn.net/binbingg/article/details/8586809
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:https://dhexx.cn/news/show-2510071.html

相关文章

车联网V2X介绍之:通信芯片

车联网V2X介绍之:通信芯片 参考链接:https://zhuanlan.zhihu.com/p/115276002 目前,我国产业化进程逐步加快,产业链上下游企业已经围绕LTE-V2X形成包括通信芯片、通信模组、终端设备、整车制造、运营服务、测试认证、高精度定位及…

《SQL入门经典(第5版)》一一1.6 问与答

本节书摘来自异步社区出版社《SQL入门经典(第5版)》一书中的第1章,第1.6节,作者:【美】Ryan Stephens , Ron Plew , Arie D.Jones,更多章节内容可以访问云栖社区“异步社区”公众号查看。 1.6 问与答 SQL入…

看V2X标准之争,美国/欧洲/日本/中国如何布局车联网?

看V2X标准之争,美国/欧洲/日本/中国如何布局车联网? 参考链接:https://www.ednchina.com/news/20170411V2X.html 中国信息通信研究院副主任汤立波博士在近日“智能交通和安全车联网的未来之路”中分享了车联网的整个产业链,以及…

网络序、大端与小端问题

网络序、大端与小端 简单说, 小端就是把低字节存放在低地址; 大端就是把高字节存放在低地址; 为了防止网络传输数据在不同CPU平台上出现字节序不一样的情况, UPD/TCP/IP协议规定:网络字节序采用大端字节序。具体规定为…

V2X主要用到什么技术?

V2X主要用到什么技术? 参考链接:https://www.zhihu.com/question/56755587 主要是两种技术。1、基于802.11的DSRC(Dedicated Short Range Communication)(专用短距离通信)解决方案,使用5.9GHz频段。 美国交通部有计划在2019年之后强制在汽车上安装DSRC模块。有关DSRC的试…

虚拟内存、物理内存、页面文件和任务管理器介绍

虚拟内存、物理内存、页面文件和任务管理器介绍 : 虚拟内存(Virtual Memory)是Windows管理所有可用内存的方式。 : 对于32位Windows系统,每个进程所用到的虚拟内存地址从0到2^32-1,总容量4GB, : 其中2GB是与操作系统以…

Nginx HTTPS功能部署实践

本文出处:http://oldboy.blog.51cto.com/2561410/1889346 30.1 文档目的 本文目的提高自己文档的写作能力及排版能力,加强上课所讲的内容得以锻炼也方便自己以后查阅特写此文档。 30.2 文档内容 本章内容包括:单向和双向认证的概念、openssl的介绍、Nginx单向ssl的配…

日志抓取方法总结

日志抓取方法总结 一、不在终端上显示日志 ./test >> /dev/null & 二、在后台抓取并保存日志 ./test >> /log/xxx.txt & 三、把 filename 文件里的最尾部的内容显示在屏幕上,并且不断刷新 tail -f filename 会把 filename 文件里的最尾部的内容显示在屏…

谈谈车联网--V2X技术

谈谈车联网--V2X技术 参考链接:https://mp.weixin.qq.com/s/-J8QhjURneLaqPhRA4Hmng C-V2X目前产业不断走向成熟,落地场景不断丰富,正处于规模商用的阶段。可以预见,基于蜂窝车联网(C-V2X)的“聪明的车智慧…

异常处理也有处理不了的“异常”问题

异常处理也有处理不了的“异常” 看下面的代码 int _tmain(intargc, _TCHAR* argv[]) { char* p (PCHAR)1; __try { nSize stricmp(p, "test"); } __except(EXCEPTION_EXECUTE_HANDLER) { printf("err\n"); } 这段代码运行后,异常会被正常…