为什么 C++ 代码中的 ++ (++foo) 和 (foo++) ++ 没有引发编译器错误?

新手上路,请多包涵

想问一下大佬们关于这份代码:

#include<bits/stdc++.h>
class Test{
    private:
    int a;
    public:
    void set(int x){
        a=x;
    }
    void show(){
        printf("%d\n",a);
    }
    Test operator++ (){
        ++a;
        return *this;
    }
    Test operator++ (int){
        Test t;
        t=*this;
        ++a;
        return t;
    }
};
signed main(){
    Test foo, bar;
    foo.set(10);

    bar = ++ (++foo);
    foo.show();
    bar.show();

    bar = (foo++) ++;
    foo.show();
    bar.show();
}

首先++ (++foo) 和 (fo++) ++ 好像都是未定义行为,因为修改了两次同一个变量而没有序列点,,但编译器也没有报错,输出也正常,请问这是为什么呢?
还有(foo++) ++ 是不是进行了对右值的修改?如果是,为什么编译器也没有报错,而且输出正常呢?
(使用的编译器是gcc 8.1.10)
感谢各位大佬解答qwq

输出:
11
12
12
11
和猜想一样,但竟然没有报错什么的让我很惊讶(

阅读 892
avatarAI BotBETA

回答

在 C++ 中,表达式 ++ (++foo)(foo++) ++ 确实都涉及到未定义行为(Undefined Behavior, UB),因为它们都试图在没有明确序列点(sequence point)的情况下多次修改同一个对象。未定义行为意味着编译器可以自由地以任何方式处理这种代码,包括但不限于生成看起来“正常”的输出、崩溃、产生不正确的结果等。

为什么没有编译器错误?

编译器通常不会为未定义行为生成错误或警告,因为未定义行为是合法的 C++ 语法,只是从语义上来说它的行为是不确定的。编译器只能检查语法和某些类型的语义错误,但无法预测所有可能的运行时行为,尤其是涉及到未定义行为的复杂表达式。

输出看起来正常的原因

尽管代码涉及到未定义行为,但在某些情况下,编译器可能会以一种特定的方式处理它,导致程序看起来运行正常。然而,这并不意味着这种行为是可靠的或可移植的。在不同的编译器、不同的编译器版本、甚至不同的编译优化级别下,未定义行为可能导致完全不同的结果。

关于 (foo++) ++

表达式 (foo++) ++ 确实试图对一个右值(rvalue)进行修改。foo++ 产生一个右值,它是 foo 修改前的值的副本,同时 foo 本身被增加。然后,尝试对这个右值进行后置递增操作,这是非法的,因为右值通常不能被修改(它们没有可修改的存储位置)。然而,由于 C++ 的复杂性和编译器的实现细节,这种代码有时可能不会立即导致编译器错误或运行时错误。

总结

尽管你的代码在某些编译器和编译设置下可能看起来运行正常,但它包含未定义行为,这意味着它是不可靠的,并且可能在不同的环境下产生不同的结果。编写没有未定义行为的代码是良好的编程实践,因为它可以确保程序的正确性和可移植性。

因此,建议避免编写包含类似 ++ (++foo)(foo++) ++ 这样的表达式的代码,即使它们在某些情况下看起来能“正常工作”。

1 个回答

你这个调用的时 operator++ ,是函数调用,不是内置的自增运算符。所以这里适用函数调用的规则。内置自增运算符的规则并不适用。

所以 ++(++a) 是 a.operator++().operator++()

(a++)++ 是 a.operator++(0).operator++(0)

序列点要按函数调用的规则分析。

在成员函数调用的时候,对临时对象(右值)也是可以调用非 const 成员函数的。

推荐问题
宣传栏