【问题标题】:Undefined/Unspecified/Implementation-defined behaviour warnings?未定义/未指定/实现定义的行为警告?
【发布时间】:2011-01-19 01:43:25
【问题描述】:

当编译器注意到具有未定义/未指定/实现定义行为的语句时,它不能发出警告(如果它抛出错误更好)吗?

可能将语句标记为错误,标准应该这样说,但它至少可以警告编码器。实施这样的选择是否有任何技术困难?还是根本不可能?

我得到这个问题的原因是,在像 a[i] = ++i; 这样的语句中,在到达序列点之前,它不会知道代码正在尝试引用一个变量并在同一个语句中修改它。

【问题讨论】:

  • 另外一个重要原因是,一个完美的 n00b 在没有导师的情况下通过教科书学习,不了解常见问题解答,可能正在编写一些代码,其中单个语句可能未定义;该编译单元中的所有剩余代码都未定义;他会继续不知情。例如。几年前我还是一岁。

标签: c++ c compiler-warnings undefined unspecified


【解决方案1】:

这一切都归结为

  • 实施质量:警告越准确和有用,效果就越好。总是为每个程序打印:“该程序可能会或可能不会调用未定义的行为”然后对其进行编译的编译器非常无用,但符合标准。值得庆幸的是,没有人编写这样的编译器:-)。

  • 易于确定:编译器可能不容易确定未定义的行为、未指定的行为或实现定义的行为。假设你有一个 5 层深的调用堆栈,const char * 参数从顶层传递到链中的最后一个函数,最后一个函数调用 printf()const char * 作为第一个函数争论。你想让编译器检查const char * 以确保它是正确的吗? (假设第一个函数对该值使用文字字符串。)如果从文件中读取 const char *,但您知道该文件将始终包含正在打印的值的有效格式说明符,该怎么办?

  • 成功率:编译器可能能够检测到许多可能未定义、未指定等的构造;但“成功率”非常低。在这种情况下,用户不希望看到很多“可能未定义”的消息——过多的虚假警告消息可能会隐藏真正的警告消息,或者提示用户在“低警告”设置下进行编译。这很糟糕。

对于您的特定示例,gcc 会发出有关“可能未定义”的警告。它甚至会警告printf() 格式不匹配。

但是,如果您希望编译器能够对所有未定义/未指定的情况进行诊断,则尚不清楚这是否应该/可以工作。

假设您有以下内容:

#include <stdio.h>
void add_to(int *a, int *b)
{
    *a = ++*b;
}

int main(void)
{
    int i = 42;
    add_to(&i, &i); /* bad */
    printf("%d\n", i);
    return 0;
}

编译器是否应该警告您*a = ++*b; 行?

正如gf 在 cmets 中所说,编译器无法跨翻译单元检查未定义的行为。经典示例是在一个文件中将变量声明为指针,并在另一个文件中将其定义为数组,请参阅comp.lang.c FAQ 6.1

【讨论】:

  • 别忘了“一旦涉及到对已编译源的外部调用,编译器就无法确定未定义的行为”
  • @gf:好点。我在写答案时正在考虑它,但后来忘记了。谢谢!
  • 被接受以展示编译器并不总是确定性的可能场景并提供详尽的推理。
  • 使“导出”模板功能难以实现的一件事是,必须检查 ODR 禁止影响多个翻译单元的事情:如果从两个 TU 对单个模板定义进行两次调用,那么您将面临对两个 TU 的名称查找,这 应该 查找相同的声明。编译器不能简单地忽略这里的不一致(根据 EDG 成员),因为这种不一致将直接影响其解析这些依赖名称的行为(并且编译器并不真的想对此进行段错误)。
【解决方案2】:

不同的编译器捕获不同的条件;大多数编译器都有警告级别选项,GCC 特别有很多,但是 -Wall -Werror 会打开大多数有用的选项,并强制它们出错。在 VC++ 中使用 \W4 \WX 进行类似的保护。

在 GCC 中,您可以使用 -ansi -pedantic,但它所说的是 pedantic,并且会引发许多不相关的问题,并且难以使用大量第三方代码。

无论哪种方式,因为编译器会捕获不同的错误,或者针对同一错误产生不同的消息,因此使用多个编译器很有用,不一定是为了部署,而是作为穷人的静态分析。 C 代码的另一种方法是尝试将其编译为 C++。 C++ 更强的类型检查通常会产生更好的 C 代码;但请确保如果您希望 C 编译正常工作,请不要专门使用 C++ 编译;您可能会介绍 C++ 特定的功能。同样,这不需要部署为 C++,而只是用作附加检查。

最后,编译器通常在构建性能和错误检查之间取得平衡;详尽地检查将花费许多开发人员无法接受的时间。出于这个原因,存在静态分析器,对于 C,有传统的 lint 和开源夹板。 C++ 的静态分析更复杂,而且工具通常非常昂贵。我用过的最好的之一是 Programming Research 的 QAC++。我不知道有任何知名的免费或开源 C++ 分析器。

【讨论】:

  • 感谢您的解释和建议;我会看看 split 做了什么(+1)。
【解决方案3】:

gcc 在这种情况下确实会发出警告(至少在 -Wall 的情况下):

#include <stdio.h>

int main(int argc, char *argv[])
{
  int a[5];
  int i = 0;

  a[i] = ++i;

  printf("%d\n", a[0]);

  return 0;
}

给予:

$ make
gcc -Wall main.c -o app
main.c: In function ‘main’:
main.c:8: warning: operation on ‘i’ may be undefined

编辑:

如果您出于某种原因不想使用-Wall,快速阅读man page 会发现-Wsequence-point 会这样做。

【讨论】:

  • 在调用编译器时要传递任何特定选项以使其发出警告?调用它通常不会给出任何错误或警告。
  • -Wsequence-point 会做,我用-Wall 这可能是个好主意。
  • 我不知道 GCC 会警告这些行为,根据我使用 GCC 和 VC++ 的经验,我没有看到任何行为;我总是在 GCC 中使用-Wall,在 VC++ 中使用警告级别 3,但可能我没有编译一些具有未定义行为的代码;但我不知道-Wsequence-point。无论如何谢谢:)
  • @legends2k@ 为什么只有 \W3 用于 VC++?对 GCC 使用 \W4 \WX 和 -Wall -Werror。关于不知道具体的警告选项:gcc.gnu.org/onlinedocs(他们都在那里)。
【解决方案4】:

相反,编译器不需要对未定义的行为进行任何类型的诊断:

§1.4.1:
可诊断规则集由本国际标准中的所有句法和语义规则组成,但那些包含“不需要诊断”或被描述为导致“未定义行为”的明确表示法的规则除外。

强调我的。虽然我同意这可能很好,但编译器在尝试符合标准时遇到了很多问题,更不用说教程序员如何编程了。

【讨论】:

    【解决方案5】:

    当您在语法上仍然正确的情况下做一些超出语言规范的事情时,GCC 会尽可能多地发出警告,但超出特定点时必须充分告知。

    您可以使用 -Wall 标志调用 GCC 以查看更多信息。

    【讨论】:

      【解决方案6】:

      如果您的编译器不会对此发出警告,您可以尝试使用 Linter。

      Splint 是免费的,但只检查 C http://www.splint.org/

      Gimpel Lint 支持 C++,但价格为 389 美元 - 也许可以说服您的公司购买一份副本? http://www.gimpel.com/

      【讨论】:

        猜你喜欢
        • 2011-05-20
        • 2017-10-26
        • 1970-01-01
        • 2016-01-14
        • 2017-12-27
        • 2016-03-19
        • 2021-09-14
        • 1970-01-01
        相关资源
        最近更新 更多