翻译《有关编程、重构及其他的终极问题?》——28.如果你可以使用简单的函数就不要使用宏

翻译《有关编程、重构及其他的终极问题?》——28.如果你可以使用简单的函数就不要使用宏

标签(空格分隔): 翻译 技术 C/C++
作者:Andrey Karpov
翻译者:顾笑群 - Rafael Gu
最后更新:2017年07月13日


28.如果你可以使用简单的函数就不要使用宏

下面这段代码摘自ReactOS项目,其中包含的一个错误被PVS-Sudio分析器诊断为:V640 The code’s operational logic does not correspond with its formatting. The second statement will always be executed. It is possible that curly brackets are missing(译者注:大意是操作的逻辑和代码格式不匹配,所以第二行声明会始终被执行,这很可能是应为大括号的缺失导致的)。

#define stat64_to_stat(buf64, buf)   \
    buf->st_dev   = (buf64)->st_dev;   \
    buf->st_ino   = (buf64)->st_ino;   \
    buf->st_mode  = (buf64)->st_mode;  \
    buf->st_nlink = (buf64)->st_nlink; \
    buf->st_uid   = (buf64)->st_uid;   \
    buf->st_gid   = (buf64)->st_gid;   \
    buf->st_rdev  = (buf64)->st_rdev;  \
    buf->st_size  = (_off_t)(buf64)->st_size;  \
    buf->st_atime = (time_t)(buf64)->st_atime; \
    buf->st_mtime = (time_t)(buf64)->st_mtime; \
    buf->st_ctime = (time_t)(buf64)->st_ctime; \

int CDECL _tstat(const _TCHAR* path, struct _stat * buf)
{
  int ret;
  struct __stat64 buf64;

  ret = _tstat64(path, &buf64);
  if (!ret)
    stat64_to_stat(&buf64, buf);
  return ret;
}

解释
这次的代码实例比较长,幸运的是还挺简单的,所以应该不难理解。

这段代码的大概意图是:通过_tstat64()函数获得文件的信息数据,然后把这些数据放入_stat类型的结构体中,我们就使用stat64_to_stat宏去实现这个功能。

这个宏其实没有被正确的实现,因为这些操作在执行时没有用大括号归到一组,所以结果就是在条件判断语句后的执行范围只有宏的第一行被执行了。如果你在代码中展开宏,你会发现如下:

if (!ret)
  buf->st_dev   = (&buf64)->st_dev;
buf->st_ino   = (&buf64)->st_ino;
buf->st_mode  = (&buf64)->st_mode;

这样主要的后果就是大部分(译者注:除了宏里的第一行buf->st_dev外)架构成员变量都被复制了,而不管文件信息有没有成功获得。

正确的代码
最简单的解决办法就是给宏定义增加大括号。增加do {…} while(0)是一个更好一点的方式。然后在宏和函数的后面,你可以跟上一个分号。

#define stat64_to_stat(buf64, buf)   \
  do { \
    buf->st_dev   = (buf64)->st_dev;   \
    buf->st_ino   = (buf64)->st_ino;   \
    buf->st_mode  = (buf64)->st_mode;  \
    buf->st_nlink = (buf64)->st_nlink; \
    buf->st_uid   = (buf64)->st_uid;   \
    buf->st_gid   = (buf64)->st_gid;   \
    buf->st_rdev  = (buf64)->st_rdev;  \
    buf->st_size  = (_off_t)(buf64)->st_size;  \
    buf->st_atime = (time_t)(buf64)->st_atime; \
    buf->st_mtime = (time_t)(buf64)->st_mtime; \
    buf->st_ctime = (time_t)(buf64)->st_ctime; \
  } while (0)

建议
我不能说宏是我的最爱,但我知道如果没有它们,简直无法完成代码,在C语言中尤其如此。尽管如此,我还是尽量避免使用它们,而且建议你们也尽量避免过渡使用它们。我对宏由敌意主要由三个原因:

  • 难以调试
  • 很容易出错
  • 让代码更难理解,特别是在一个宏中使用另外一些宏的情况

还有很多其他错误都是和宏有关系。上面那个我给粗的例子很清晰的表明了有时我们根本不需要宏。我真的不能明白为什么作者不使用一个简单的函数。

下面是函数比宏好的地方:

  • 代码更简单,你不用再被逼花额外的时间去写宏已经对齐一些奇怪的语句
  • 代码更可靠(上面的错误,如果再用函数代替宏的情况下,根本不可能发生)

如果说有什么缺点的话,我能想到的就是函数需要优化,是的,函数是被调用了,但这并不是说一点问题都没有了。

让我们假设这个函数对我们很关键,然后考虑如何进行优化。首先,你可以使用inline关键字,是不是很棒。另外,如果把函数声明为静态的也是合适的。而且我认为编译器有足够的能力把函数编译到每个编译单元中,而不是只生成一个分离的版本(译者注:分裂的版本即每个对这个函数有调用的编译单元只有一个引用,执行体在一个独立的地方)。

事实上你不必担心这些,因为现在编译器已经足够聪明,即使你写的函数没有任何inline/static关键字修饰,编译器也会帮你做类似的事情,只要它觉得值得这么做。但不要纠结它怎么实现这些的细节。我们只要写出简单以及容易理解的代码就好。

按照我的方式,我会把代码改成这样:

static void stat64_to_stat(const struct __stat64 *buf64,
                           struct _stat *buf)
{
  buf->st_dev   = buf64->st_dev;
  buf->st_ino   = buf64->st_ino;
  buf->st_mode  = buf64->st_mode;
  buf->st_nlink = buf64->st_nlink;
  buf->st_uid   = buf64->st_uid;
  buf->st_gid   = buf64->st_gid;
  buf->st_rdev  = buf64->st_rdev;
  buf->st_size  = (_off_t)buf64->st_size;
  buf->st_atime = (time_t)buf64->st_atime;
  buf->st_mtime = (time_t)buf64->st_mtime;
  buf->st_ctime = (time_t)buf64->st_ctime;
}

实际上,我们在这里可以有更多的改进。比如在C++中,最好不要传入指针,而是传入引用。在函数中没有针对指针的初步检查代码是不合适的,当然,这是另外一个不同的话题了,我不想在一个关于宏的章节讨论这个。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值