确定"未知的评估顺序"

Wol*_*olf 21 c c++ static-analysis operator-precedence cppcheck

从版本1.80开始,Cppcheck告诉我

表达式'msg [ipos ++] =校验和(&msg [1],ipos-1)'取决于副作用的评估顺序

在这个代码序列中(简化,data是一个变量)

BYTE msg[MAX_MSG_SIZE];  // msg can be smaller, depending on data encoded
int ipos = 0;
msg[ipos++] = MSG_START;
ipos += encode(&msg[ipos], data);
msg[ipos++] = checksum(&msg[1], ipos-1);  // <---- Undefined Behaviour?
msg[ipos++] = MSG_END;   // increment ipos to the actual size of msg
Run Code Online (Sandbox Code Playgroud)

并将此视为错误,而不是可移植性问题.

它是C代码(包含在C++主导的项目中),使用C++ 98兼容编译器编译,同时按预期运行数十年.Cppcheck使用C++ 03,C89,自动检测语言运行.

我承认代码应该更好地重写.但在此之前,我试图弄清楚:它真的依赖于评估顺序吗?根据我的理解,正在首先评估正确的操作数(它需要在调用之前),然后msg[ipos]使用ipos最后完成的增量进行赋值(to ).

我错了这个假设,还是只是假阳性?

Joh*_*nck 41

此代码确实依赖于评估顺序,其方式未明确定义:

msg[ipos++] = checksum(&msg[1], ipos-1);
Run Code Online (Sandbox Code Playgroud)

具体而言,未指定是否ipos++ipos-1评估之前或之后递增.这是因为在完整表达式(the )的末尾没有" 序列点 " .=;

函数调用是一个序列点.但这只能保证ipos-1在函数调用之前发生.它不能保证ipos++在之后发生.

似乎应该以这种方式重写代码:

msg[ipos] = checksum(&msg[1], ipos-1);
ipos++; // or ++ipos
Run Code Online (Sandbox Code Playgroud)

  • @Wolf:是的,函数调用是一个序列点.但这只能保证`ipos-1`在函数调用之前发生.它并不保证`ipos ++'之后会发生. (6认同)
  • @Wolf编译器可以在评估RHS之前自由计算编写赋值的内存地址(从而更新`ipos`). (2认同)
  • 新标准所说的是无关紧要的,因为问题明确是关于C89,C++ 98和C++ 03. (2认同)

Lun*_*din 8

评价的顺序的操作数=是不确定的.首先,代码依赖于未指定的行为.

更糟糕的是,ipos在同一个表达式中使用了两次,其间没有序列点,出于不相关的目的 - 这会导致未定义的行为.

C99 6.5

在前一个和下一个序列点之间,对象的存储值最多只能通过表达式的计算来修改一次.此外,先前的值应该只读以确定要存储的值.

同一文本适用于C90,C99,C++ 98和C++ 03.在C11和C++ 11中,措辞已经改变,但含义是相同的.这是未定义的行为,前C++ 11.

编译器不需要对未定义的行为进行诊断.你很幸运.这不是误报 - 您的代码包含一个严重的错误,一直到原始的C代码.

  • 对于它的价值,并不是编译器发出警告 - 它是一个静态分析工具(cppcheck,它是免费的,不是非常精细,但很有用). (2认同)