【问题标题】:Expression 'i < 0' is always false表达式 'i < 0' 始终为假
【发布时间】:2018-01-06 17:10:41
【问题描述】:

对于下面的sn-p:

size_t i = 0;
std::wstring s;
s = (i < 0)   ? L"ABC" : L"DEF";
s = (i != -1) ? L"ABC" : L"DEF";

PVS-Studio 分析记录第一个条件 i &lt; 0 的警告,如预期的那样:

V547Expression 'i &lt; 0' is always false. Unsigned type value is never &lt; 0. test_cpp_vs2017.cpp 19

例如,为什么 PVS 不发出任何关于第二个、也是可疑情况 i != -1 的警告总是如此?

【问题讨论】:

  • @JeffUK 不,它不会单独警告i != -1
  • 好的,viva64.com/en/w/V3022 这篇文章似乎很好地解释了它,当它说“分析器不会警告每一个总是正确或错误的条件;它只会在错误高度严重时诊断这些情况很有可能。”
  • @JeffUK,条件i != -1总是正确的!
  • @JanHudec 我不同意,它不像i&lt;0 那样固有 false,但此代码的静态分析表明i 在发生此检查时始终为0,因此@ 987654331@ 在代码中此时是多余的。 PVS-Studio 只是选择只警告前者而不是后者。
  • 奇怪的是,当isize_ti &lt; 0 永远不会为真,而当isize_ti &lt;= -1 将永远为真。

标签: c++ static-analysis unsigned pvs-studio


【解决方案1】:

有正确的重要答案,但我想澄清一下。不幸的是,测试示例的格式不正确。我们可以这样写:

void F1()
{
  size_t i = 0;
  std::wstring s;
  s = (i < 0)   ? L"ABC" : L"DEF";
  s = (i != -1) ? L"ABC" : L"DEF";
}

在这种情况下,分析器会发出两个V547 警告:

  • V547 表达式“i
  • V547 表达式 'i != - 1' 总是 真的。控制台应用程序1.cpp 16

V519也会发生,但与问题无关。)

因此,第一个 V547 警告是打印,因为无符号变量不能小于零。变量的值也无关紧要。 发出第二个警告是因为分析器的反应是 0 已分配给变量 i,并且此变量在任何地方都没有改变。

现在我们再写一个测试例子,让分析器对变量i的值一无所知:

void F2(size_t i)
{
  std::wstring s;
  s = (i < 0)   ? L"ABC" : L"DEF";
  s = (i != -1) ? L"ABC" : L"DEF";
}

现在将只有一个 V547 警告:

  • V547 表达式“i

分析器对(i != -1) 条件一无所知。这是完全正常的,例如,可以与npos 进行比较,正如已经注意到的那样。

如果有人决定使用 PVS-Studio 测试源示例,我写了这篇文章,这是不可能的。当这个人看到两个警告时,他会感到惊讶,尽管有人讨论过只有一个。

【讨论】:

  • 感谢您的回答。我会争辩说,“测试示例的格式不正确”这句话是不幸的。我的问题中的代码 sn-p 没有任何不正确之处。这可能不足以理解 PVS-Studio 的行为,但这并不意味着它是不正确的。
  • 也许,我的回答太强硬了。英语不是我的母语,所以,我有时会使用不太恰当的词。我只是想指出一个例子和一个问题会让程序员感到困惑。
  • 安德烈卡波夫明白了。同样在这里。你的笔记完全有效。谢谢
【解决方案2】:

当一个值被转换为无符号时,如果该值不能由无符号类型表示,那么该值将被转换为 是的值(或者更确切地说, 值) 可表示,并且与原始值以可表示值的数量为模(最大可表示值 + 1 == 2n 其中 n 是位数)一致。 p>

因此没有什么需要警告的,因为有些值可能是假的(只要我们只单独分析那个表达式。i 总是 0,所以条件总是真,但是为了能够证明这一点,我们必须考虑程序的整个执行过程)。

-1 与 m - 1 模 m 一致,因此 -1 总是转换为最大可表示值。

【讨论】:

  • > 没什么可警告的 废话。由于运算符另一侧的无符号类型,将-1 转换为非常大的值可能令人惊讶。在各种正确的、符合标准的构造中,有很多需要警告的地方。此外,此处未完全指定行为;结果是实现定义的。如果size_t 是 16 位宽,则得到 65535,以此类推;漂亮且可移植的 -1 常量已变成编译器特定的数字。
  • @Kaz:更糟糕的是,标准中没有任何规定会禁止像具有 32 位 int 但 16 位地址空间的 DSP 将 size_t 设为 @ 987654327@,其值都可以表示为int。在这种情况下,如果左边的运算符是 (size_t)-1,它将被提升为 int 值 65535,比较不等于 -1。
  • @supercat 确实;同样的事情的另一个简单的例子:给定unsigned char uc = -1uc == -1 失败。 uc 是 255(在许多平台上),它提升为 int 类型。我们在 255 和 -1 之间进行了 int == int 比较,但失败了。如果将 -1 与 unsigned 进行比较,如果它们可以是 typedef-d 与提升为 int 的小类型,则存在缺陷。
【解决方案3】:

因为那将是一个无用的、无效的警告。 size_t 是无符号类型,由于整数转换的工作方式(参见 [conv.integral]/2),-1 转换(此处隐含)到 size_t 等于 SIZE_MAX

考虑到这是 std::string::npos 在 libstdc++ 中的实际定义:

static const size_type  npos = static_cast<size_type>(-1);

如果 PVS-Studio 警告 i != -1,是否还需要警告 i != std::string::npos

另一方面,无符号值永远不会小于 0,因为它是无符号的,所以 i &lt; 0 可能不是程序员想要的,因此警告是有道理的。

【讨论】:

  • 我希望会有关于隐式转换的警告,这可能是无意的
  • @ratchetfreak 是的。
  • @ratchetfreak 实际上更有说服力的可能是this
  • 关于some_unsigned_variable != -1 的警告不会没有用!在将其用作惯用语的代码中确实会是一个讨厌,但在不使用它的代码中可能非常有用,可以帮助某人找到错误。但是,使用负常量和无符号类型的代码往往出现在编译器和平台头文件中,因此必须清理这些代码以免触发误报警告或使用一些 #pragmas 来禁用它或其他。
  • @Kaz:作为一名代码审查者,我会对无符号变量和 -1 之间的直接比较感到愤怒。该代码将具有两种明确含义之一,具体取决于无符号值是否足够大以避免提升,但如果它明确了它实际要求的内容,我会认为代码更清晰,例如与(uint32_t)-1 比较,如果这是我们想要的。
【解决方案4】:

这是由于两种情况下的隐式积分转换size_t 必须 是至少 16 位的无符号类型,在您的情况下,它的大小足够 cf。 int,如果一个参数是size_t,另一个是int,那么int 参数将转换为size_t

在评估 i &lt; 0 时,0 被转换为 size_t 类型。两个操作数都是size_t,所以表达式总是false

在评估 i != -1 时,-1 也会转换为 size_t。此值为std::numeric_limits&lt;size_t&gt;::max()

参考:http://en.cppreference.com/w/cpp/language/implicit_conversion

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-30
    • 2019-12-12
    • 1970-01-01
    • 1970-01-01
    • 2012-08-20
    • 2016-04-19
    • 1970-01-01
    相关资源
    最近更新 更多