C/C++ 在assert中不要使用带有副作用的表达式

最近在项目中使用assert出现了一个bug,排查了半天才定位到错误,在讲这个陷阱之前先铺垫一下assert基础知识。 assert是 C/C++ 中的一个,定义在assert.h文件中。assert 宏将在 expression 计算结果为 false(0) 时打印错误信息并调用abort函数终止进程。 如果 expressiontrue (非0),则不执行任何操作。 错误信息包括失败的expression、源文件名以及失败的行号。如果定义了NDEBUG宏,assert不会被启用assert适用于捕捉不应该发生的非法情况,而不是错误处理,因为错误是可以预料的。

现在讲讲我遇到的坑,最近准备将项目中使用的一个nfs客户端库切换到新版本,并想对比下新版本对性能提升多少,所以我准备一个简单测试程序,向nfs服务器写入1G数据, 测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
char buf[1024*1024];
memset(buf, 1 , sizeof(buf));

struct nfsfh *fd;
int r = nfs_open("file1", O_RDWR, &fd);
if (r != 0)
{
printf("Failed to create file\n");

return EXIT_FAILURE;
}

int i;
for (i = 1; i <= 1024; i++) // Write 1G
{
assert(sizeof(buf) == nfs_write(fd, buf, sizeof(buf)));
}
nfs_close(fd);

return EXIT_SUCCESS

当运行测试程序,程序正常结束。检查文件大小,也是符合预期。突然想起是测性能,所以我在项目cmake中指定编译为Release版本,之前为Debug版本。然后运行,发现文件虽然在nfs服务器中创建了,但是文件大小为0。这让我一脸懵,因为如果向文件写入数据失败一定会触发断言,程序应该崩溃才对。最开始怀疑是不是新版本库有bug,排查了后发现没问题。想了半天突然想起来,我现在是Release版本,cmake 会在编译时定义NDEBUG宏,assert不会被启用,所以nfs_write函数根本没有执行。所以千万不要在assert中使用带有副作用的表达式,不然发现程序在Debug中正常运行,在Release中会产生不可预料的后果

有副作用的表达式指的是除了返回一个值之外,还会修改进程状态的表达式。这些状态变化可能包括:修改变量、执行IO、影响文件、网络、线程状态等。