C++ 具有未定义行为且从未实际执行的表达式是否会使程序出错?

C++ 具有未定义行为且从未实际执行的表达式是否会使程序出错?,c++,language-lawyer,undefined-behavior,C++,Language Lawyer,Undefined Behavior,在许多关于未定义行为(UB)的讨论中,有人提出这样一种观点,即在程序中存在任何构造,只要该构造在程序中具有UB,则一致性实现就必须执行任何操作(完全不包括任何操作)。我的问题是,即使在UB与代码执行相关的情况下,是否也应该从这个意义上考虑这一点,而标准中规定的行为(否则)规定不应该执行有问题的代码(这可能是针对程序的特定输入;它可能在编译时不可判定) 用更非正式的措辞来说,《UB的味道》是否要求一致的实现来决定整个程序的臭味,甚至拒绝正确执行程序中行为定义完美的部分。一个例子是 #include

在许多关于未定义行为(UB)的讨论中,有人提出这样一种观点,即在程序中存在任何构造,只要该构造在程序中具有UB,则一致性实现就必须执行任何操作(完全不包括任何操作)。我的问题是,即使在UB与代码执行相关的情况下,是否也应该从这个意义上考虑这一点,而标准中规定的行为(否则)规定不应该执行有问题的代码(这可能是针对程序的特定输入;它可能在编译时不可判定)

用更非正式的措辞来说,《UB的味道》是否要求一致的实现来决定整个程序的臭味,甚至拒绝正确执行程序中行为定义完美的部分。一个例子是

#include <iostream>

int main()
{
    int n = 0;
    if (false)
      n=n++;   // Undefined behaviour if it gets executed, which it doesn't
    std::cout << "Hi there.\n";
}
#包括
int main()
{
int n=0;
if(false)
n=n++;//如果执行了未定义的行为,则不会执行
标准::cout
如果标量对象上的副作用相对于etc未排序

副作用是执行环境(1.9/12)状态的更改。更改是一种更改,而不是一个表达式,如果进行计算,可能会产生更改。如果没有更改,则没有副作用。如果没有副作用,则相对于其他任何内容,没有副作用未排序

这并不意味着任何从未执行过的代码都是UB自由的(尽管我很确定大多数代码都是如此)。标准中UB的每次出现都需要单独检查(删掉的文本可能过于谨慎;请参见下文)

标准还说,


执行格式良好的程序的一致性实现应产生相同的可观察行为 作为使用相同程序的抽象机器的相应实例的可能执行之一 和相同的输入。但是,如果任何此类执行包含未定义的操作,则此 标准对使用该输入执行该程序的实现没有任何要求 关于第一个未定义操作之前的操作)

(强调矿山)


据我所知,这是唯一一个说明短语“未定义行为”含义的规范性参考:程序执行中的未定义操作。没有执行,就没有UB。

在安全关键嵌入式系统的上下文中,发布的代码将被视为有缺陷:

  • 准则不应通过准则审查和/或标准合规性(MISRA等)
  • 静态分析(lint、cppcheck等)应将其标记为缺陷
  • 一些编译器可以将此标记为警告(也意味着存在缺陷)
  • 不,例如:

    struct T {
        void f() { }
    };
    int main() {
        T *t = nullptr;
        if (t) {
            t->f(); // UB if t == nullptr but since the code tested against that
        }
    }
    

    在一般情况下,我们在这里能说的最好的话就是这取决于情况

    在处理不确定值时,会出现一种答案为“否”的情况。最新的草案明确指出了一些例外情况,但代码示例清楚地表明了这可能是多么微妙:

    [示例:

    -[结束示例]

    所以这一行代码:

    return b ? d : 0;
    
    只有当
    b
    true
    时,它才是未定义的。这似乎是一种直观的方法,如果我们阅读的话,John Regehr似乎也是这样看待它的

    在这种情况下,答案是肯定的,代码是错误的,即使我们没有调用调用未定义行为的代码:

    constexpr const char *str = "Hello World" ;      
    
    constexpr char access()
    {
        return str[100] ;
    }
    
    int main()
    {
    }
    
    clang
    选择使
    access
    出错,即使它从未被调用()。

    决定程序是否执行整数除以0(即UB)通常等同于停止问题。通常,编译器无法确定这一点。因此,仅仅存在可能的UB在逻辑上不会影响程序的其余部分:标准中对此的要求将要求每个编译器供应商在编译器中提供停止问题解算器

    更简单的是,以下程序只有在用户输入0时才具有UB:

    #include <iostream>
    using namespace std;
    
    auto main() -> int
    {
        int x;
        if( cin >> x ) cout << 100/x << endl;
    }
    
    #包括
    使用名称空间std;
    auto main()->int
    {
    int x;
    
    如果(cin>>x)cout在固有的未定义行为(如n=n++)和根据运行时的程序状态(如int的x/y)可以定义或未定义行为的代码(如x/y)之间有一个明确的划分。在后一种情况下,除非y为0,否则程序必须运行,但在第一种情况下,编译器被要求生成完全不正常的代码egitimate-它有权拒绝编译,它可能只是不能“防弹”这样的代码,因此它的优化状态(寄存器分配,其中的值可能在读取后被修改的记录等)已损坏,导致该源代码和周围源代码的假机器代码。可能是早期分析识别出“a=b++”前一个if的情况和生成的代码将跳过一个两字节的指令,但是当遇到n=n++时,没有输出任何指令,因此if语句将跳转到以下操作码的某个位置。无论如何,这只是游戏结束。将“if”放在前面,或者甚至将其包装到另一个函数中,都没有记录为“包含”未定义的行为…代码位没有被未定义的行为污染-标准一致地说“程序有未定义的行为”“

    一个C编译器可以在程序进入一个没有定义的事件序列的状态时做任何它想做的事情,通过这个状态,程序可以避免在将来的某个时候调用未定义的行为(请注意,任何没有任何副作用、没有编译器需要识别的退出条件的循环,都会调用自身的未定义行为)。在这种情况下,编译器的行为既不受时间法则的约束,也不受时间法则的约束
    #include <iostream>
    using namespace std;
    
    auto main() -> int
    {
        int x;
        if( cin >> x ) cout << 100/x << endl;
    }
    
    void maybe_launch_missiles(void)
    {      
      if (should_launch_missiles())
      {
        arm_missiles();
        if (should_launch_missiles())
          launch_missiles();
      }
      disarm_missiles();
    }
    int foo(int x)
    {
      maybe_launch_missiles();
      return x<<1;
    }
    
    #include <limits.h>
    
    #if LONG_MAX == 0x7FFFFFFF
    typedef int longish;
    #else
    typedef long long longish;
    #endif
    
    long test(long *x, long *y)
    {
        if (*x)
        {
            if (x==y)
                *y = 1;
            else
                *(longish*)y = 1;
        }
        return *x;
    }