宏一定要用大写

来源:互联网 发布:棉先生衣服怎么样 知乎 编辑:程序博客网 时间:2024/05/22 10:27

前言

被一个宏折腾了2次,才偶然发现问题。问题挺隐蔽的,也许是自己对BUG出现后的表现,还不太敏感。
以前认为宏需要大写,可能就是看着舒服,维护方便。
经过这个bug后,增加了体会。宏大写可以降低调试的难度,提高代码的清晰度。

BUG回放

最近维护的linux工程,为了出win版。代码中不可避免的用宏隔开,或定义了一些在win和linux下都能用的宏。

在工程中使用了, pthreads-w32库。
封装了一个简单的socket类, 对socket基本操作,进行了封装。
当socket任务完成时,需要关闭socket句柄。
linux版没问题。
在win版下,调用closesocket(h_sock), 直接报错了,陷在_close里面。
当时就想不明白,windows下关闭一个socket句柄不就是要调用closesocket么?还能咋样?
因为不关socket句柄,在频繁操作(e.g. 封装了一个函数,里面连接服务器,发送数据,关闭句柄)的情况下,内存不到一个小时,就被吃的差不多了。
既然不能调用closesocket,我就理所当然的认为,一定是被pthreads-w32坑了。
当时用CloseHandle来代替closesocket,测试的时间不长,没有内存泄漏了,暂时先这样。
这是一个月之前的事情。

前天,发现运行这个demo的计算机,长时间运行以后,再开socket服务,已经不行了,bind失败。这时候知道了,用CloseHandle来代替closesocket的后果,产生了句柄泄漏。当频繁调用那个封装函数时(e.g. 发一个小文件,就调用一次封装函数)。当运行几个小时后,socket句柄就被用的差不多了。

下面就是必须要解决这个问题,究竟咋的了。
知道调用closesocket会崩溃,那就单步进去崩着看。
当崩溃的时候,落在_close里面。当时感到好无助:( 这closesocket参数就一个socket句柄,调用一下就完事了,还能怎样?

突然想到,这不是一个API么?为啥会陷在源码里面?迷惑啊。
回到closesocket的调用点,不是有源码么? 那F12一下。
F12到位后,被惊呆了。看到代码是一个宏。。。

#define closesocket(sk) close(sk)

这使我深深的体会到,宏为什么要大写。
最近才开始正式维护这个工程,我不太确定这个可爱的宏是不是我写的。去查svn可以确定作者,但是该工程从旧版升级上来,有几个svn, 查起来不方便。这个宏就是我写的,又咋的了:)

问了老同事,他说这个宏以前就有。
因为这个工程以前是linux版,估计原作者也是为了以后这个类能兼容win版,就定义了这个迷惑性很强的宏。但是这个宏在这个socket封装类中并没有使用。因为关闭句柄时,直接调用close(sk)就可以。这个宏就埋在这里了。

其实这个宏可以写成下面这样, 让非原作者不要入坑太深。当包含了适当的头文件后,不进该坑。

#ifndef closesocket#define closesocket(sk) close(sk)#endif // #ifndef closesocket

如果用大写来表示宏,就很明了,使用者绝对不会入坑, 一看到就知道是宏。
宏的名称也不要和API的名称相同。

#ifndef SAFE_CLOSE_SOCKET#ifndef WIN32    #define SAFE_CLOSE_SOCKET(sk) \    if (INVALID_SOCKET != (sk)) { \        close((sk)); \        (sk) = INVALID_SOCKET; \    }#else    #define SAFE_CLOSE_SOCKET(sk) \    if (INVALID_SOCKET != (sk)) { \        closesocket((sk)); \        (sk) = INVALID_SOCKET; \    }#endif#endif // #ifndef SAFE_CLOSE_SOCKET