帮我理解这段奇怪的C++代码

6

这是我们以前C++考试中的一道问题。这段代码让我很疯狂,有人能解释一下它是做什么的,尤其是为什么吗?

int arr[3]={10,20,30};
int *arrp = new int;

(*(arr+1)+=3)+=5;
(arrp=&arr[0])++;

std::cout<<*arrp;

26
在我看来,这似乎是一场相当糟糕的C++考试。 - anon
2
他们是从IOCCC拿来考试题的吗? - Matti Virkkunen
3
我不想浪费时间在这个上面,但我认为其中两行代码会导致未定义行为,因为它们在没有中间序列点的情况下多次写入同一对象。 - CB Bailey
1
@Matti:是的,+= 的结果是一个左值。 - CB Bailey
1
不过,我想老师应该期望有人打电话给28。不知道他会如何对待一个用UB压制他的学生 :D - Matthieu M.
显示剩余8条评论
6个回答

13

这个语句在没有中间序列点的情况下两次写入对象*(arr+1),因此具有未定义行为

(*(arr+1)+=3)+=5;

该语句在没有中间序列点的情况下两次写入对象arrp,因此具有未定义行为

(arrp=&arr[0])++;

这段代码可能导致任何事情发生。

参考:ISO/IEC 14882:2003 5 [expr]/4:“在前一个和下一个序列点之间,标量对象的存储值最多仅通过表达式的评估被修改一次。”


9
(*(arr+1)+=3)+=5;

arr + 1 - 索引为1的元素
*(arr + 1) - 这个元素的值
(arr + 1) += 3 - 增加3
(
(arr+1)+=3)+=5 - 增加5;

因此 arr[1] 的值为28

(arrp=&arr[0])++;

arr[0] - 元素0的值
&arr[0] - 元素0的地址
arrp=&arr[0] - 将 arrp 设置为指向元素0的指针
(arrp=&arr[0])++ - 将 arr 设置为指向元素1的指针

结果:28


1
但是+= 3+= 5之间没有序列点,它们都作用于同一对象? - CB Bailey
@Charles:我很确定它是有效的。否则,+=运算符(或任何赋值运算符)具有非void返回类型就没有什么意义了。但是,浏览标准时,我找不到任何明确支持这一点的内容。 - jalf
@jalf: 它返回一个左值的目的是为了可以获取其地址或者在其上调用函数(意味着一个独立的序列点)。再次对其进行写入操作明显是不合法的(依我之见),但是你可以对左值进行其他操作。 - CB Bailey

6

这行代码:

(*(arr+1)+=3)+=5; 

产生的结果与这个相同(见脚注):

arr[1] += 3;
arr[1] += 5;

这一行:

(arrp=&arr[0])++;   

这段代码将产生与下面脚注中描述的相同结果:

int* arrp = arr+1;

所以这一行代码:
std::cout<<*arrp

输出结果为 28

但是这段代码会泄漏内存,因为 int *arrp = new int; 在堆上分配了一个新的 int,在通过 (arrp=&arr[0])++; 赋值后将丢失。

注:当然,我假设没有奇怪的情况。

编辑: 显然,由于 C++ 标准 5/4 的原因,某些行实际上会导致未定义的行为。所以这真的是一个糟糕的考试问题。


1
(*(arr+1)+=3)+=5;arr[1] += 3; arr[1] += 5;不相同,因为在第二个语句中引入了一个在第一个语句中不存在的顺序点。 - CB Bailey
如果没有操作数是用户定义类型,那么运算符如何被重载?原始表达式中的未定义行为有什么可能性? - CB Bailey
我认为在这种情况下,最内层的表达式首先被评估(arr+1),然后由于括号向外扩展。 - In silico
2
@In silico:是的,表达式必须是这样计算的,但是+=的两个写入不被序列点分开,因此行为是未定义的,除非您有理由说明它不是这样的? - CB Bailey

1
int arr[3]={10,20,30}; // obvious?
int *arrp = new int; // allocated memory for an int

(*(arr+1)+=3)+=5; // (1)
(arrp=&arr[0])++; // (2)

std::cout<<*arrp; // (3)

(1)

*(arr+1) 等同于 arr[1],这意味着 *(arr+1)+=3 将会把 arr[1] 增加 3,所以现在 arr[1] == 23

(*(arr+1)+=3)+=5 意味着 arr[1] 再增加 5,所以现在它是 28

(2)

arrp 将指向 arr 的第一个元素的地址(arr[0])。执行完整个语句后,指针 arrp 将被递增,因此它将指向第二个元素。

(3)

打印出 arrp 所指向的内容:即 arr 的第二个元素,也就是 28


你怎么知道 arr[1] 增加了另外的 5?同样有可能增量为 5 取代了增量为 3。或者增量为 3 取代了增量为 5。操作的顺序是未定义的。 - David Schwartz

0

好的,记住数组可以被解释为指针

int arr[3]={10,20,30};
int *arrp = new int;

创建一个由三个整数和一个新分配的值赋给的int指针arr数组。
由于赋值运算符返回已分配的值的引用,以允许多重赋值。
(*(arr+1)+=3)+=5;

等同于

*(arr+1)+=3;
*(arr+1)+=5;

*(arr + 1) 指的是数组 arr 的第一个元素,因此 arr[1] 的值增加了八。

(arrp=&arr[0])++; 将第一个数组元素的地址赋给 arrp,然后将指针递增,现在它指向第二个元素(再次是 arr[1])。

通过在 std::cout<<*arrp 中对其进行解引用,您输出了 arr[1],它现在的值为 20 + 3 + 5 = 28

因此,该代码打印出 28(并且还创建了一个内存泄漏,因为最初分配给 arrpnew int 没有被 delete)。


1
这两个代码块并不相等。您通过引入一个序列点,将操作顺序从未定义更改为已定义。在指定的代码中,增量可以以任何顺序发生,包括交错(读取1、读取2、增加1、增加2、写入1、写入2),导致两个增量都没有发生。 - David Schwartz

0

我会尝试通过以更简单的方式重写代码来回答你。

int arr[3]={10,20,30};
int *arrp = new int;

(*(arr+1)+=3)+=5;
(arrp=&arr[0])++;

std::cout<<*arrp;

=== 等于 ===

int arr[3]={10,20,30};//create array of 3 elements and assign them
int *arrp = new int;//create an integer pointer and allocate an int to it(useless)

//(*(arr+1)+=3)+=5;
arr[1] = arr[1] + 3;//arr[1] == arr+1 because it is incrementing the arr[0] pointer
arr[1] = arr[1] + 5;

//(arrp=&arr[0])++;
arrp = &arr[0];//point the integer pointer to the first element in arr[]
arrp++;//increment the array pointer, so this really is now pointing to arr[1]

std::cout<<*arrp;//just print out the value, which is arr[1]

我假设你理解指针和基本的C语言。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接