【问题标题】:Negating expression in if statement inside macro gives odd results宏内部 if 语句中的否定表达式会产生奇怪的结果
【发布时间】:2022-01-07 22:23:31
【问题描述】:

我遇到了一个有点奇怪的问题。这让我觉得答案非常明显,我只是没有看到任何东西,因为代码太简单了。

我基本上有一个名为“ASSERT”的宏,它可以确保一个值不是假的。如果是,它将向控制台写入一条消息并调试中断。 我的问题是,当断言从 std::string::find_first_of(...) 返回的索引不等于 std::string::npos 时,断言似乎根本不起作用。每次,断言都会失败。

我已经验证在调试中断发生时值不相等,所以我看不出断言是如何失败的。

在我的原始代码中,它将数据从文件读取到字符串,但问题似乎仍然存在于下面的示例中,只有一个 const std::string。

我正在处理一个更大的项目,但这里有一个重现错误的最小示例(顺便说一下,我使用的是 Visual Studio 2022 和 C++17):

#include <iostream>
#include <string>

#define ASSERT(x, msg) {if(!x) { std::cout << "Assertion Failed: " << msg << "\n"; __debugbreak(); } }

int main() {

    const std::string source = "Some string that\r\n contains the newline character: \r\n...";

    size_t eol = source.find_first_of("\r\n", 0);
    ASSERT(eol != std::string::npos, "Newline not present!");
    
    // Other code...
    
    return 0;
}

请注意,即使字符串中只有一个换行符(“\r\n”),也会发生完全相同的事情。

有趣的是,“eol”似乎在我运行的每个测试用例中都有正确的值。唯一错误的是断言,所以如果我忽略它并继续,一切都会按照我的预期运行。

我也发现了这个似乎相关的问题,但没有得出任何答案或结论: std::string::find_first_of does not return the expected value

【问题讨论】:

  • 试试#define ASSERT(x, msg) {if(!(x)) { ... } }
  • 开启所有编译器警告。该宏应至少生成六个,包括“注意:在左侧表达式周围添加括号以消除此警告”。
  • Visual Studio 对此的警告消息非常可悲。 GCC 更有帮助:godbolt.org/z/nv3Mavcb1 我保留了几个不同的编译器,以查看是否可以看到其他人遗漏的错误,以及 Matt Godbolt 的编译器资源管理器......这是快速比较小段代码结果的炸弹。跨度>
  • 请不要删除问题。我已经编辑了标题以更清楚地描述错误,并且更易于搜索。这可能是重复的,但它仍然是一个有用的问题。
  • 就像我说的:恰当的问题。任何对此感到困惑的人都需要比我们在这里提供的更多帮助。

标签: c++ std stdstring


【解决方案1】:

这是预处理器的宏替换引擎设计的简单愚蠢的意外结果。提供给宏的表达式不会像使用函数那样计算,而是在替换期间直接插入文本。

给定

#define ASSERT(x, msg) {if(!x) { std::cout << "Assertion Failed: " << msg << "\n"; __debugbreak(); } }

线

ASSERT(eol != std::string::npos, "Newline not present!");

会变成

{if(!eol != std::string::npos) { std::cout << "Assertion Failed: " << "Newline not present!" << "\n"; __debugbreak(); } }

! 仅应用于eol,将宏的预期行为更改为荒谬的行为。

添加 cmets 中推荐的额外括号

#define ASSERT(x, msg) {if(!(x)) { std::cout << "Assertion Failed: " << msg << "\n"; __debugbreak(); } }

结果

{if(!(eol != std::string::npos)) { std::cout << "Assertion Failed: " << "Newline not present!" << "\n"; __debugbreak(); } }

现在在应用! 和测试之前正在评估表达式。

因为macros are "evil",并且由于宏不使用任何特殊的、位置相关的调试宏,例如__FILE____LINE__,所以在这种情况下,我会用函数替换宏并依靠编译器的内联它的优化。

【讨论】:

  • 我刚才正在编辑我的答案来这么说。我喜欢你的回答,因为它不仅能更好地解释它,而且直到两天后我才能接受我自己的回答。谢谢和欢呼!
【解决方案2】:

已在 cmets 中提供了答案。解决方案是简单地在宏的“!x”部分中的 x 周围添加括号。这转化为以下内容(归功于 0x5453):

#define ASSERT(x, msg) {if(!(x)) { ... } }

这个解决问题的原因是,如果你扩展原始宏,它会看起来像这样(这是它的编译方式):

if(!eol != std::string::npos) {...}

这显然是不正确的,因为您想检查整个条件是否为假。像这样:

if(!(eol != std::string::npos)) {...}

添加括号可以解决这个问题。

简单,但仍然提醒您检查此类小事。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-04
    • 2015-01-12
    • 2022-12-28
    • 2022-11-04
    相关资源
    最近更新 更多