C中的+(+ k--)表达式


9

我在测试中看到了这个问题,在测试中我们必须告诉以下代码的输出。

#include<stdio.h>

int main(){
    int k = 0;
    while(+(+k--)!=0)
    k=k++;
    printf("%d\n", k);  
    return 0;
}

输出为-1。我不确定为什么会这样。

该表达式+(+k--)在C中是什么意思?


4
它是这样格式化的吗?的意思是;-)
彼得-恢复莫妮卡

3
提示:切勿在现实生活中编写此类BS代码。
Jabberwocky

5
不是 UB。看我的答案。
dbush

@ Peter-ReinstateMonica是的,它的格式是这样的。
Ankur Gautam

2
由于其人为的性质和怪异的曲折- k=k++是不确定的,但不是不确定的,因为由于混淆的条件而从未执行过它-我投票结束于此问题的重复。
Steve Summit

Answers:


10

[为了记录,自从被接受并投票以来,我已经对该答案进行了相当大的编辑。不过,它仍然说基本相同的内容。]

这段代码深深地,有意地使人困惑。它包含一个可怕的未定义行为的狭窄实例。基本上不可能确定提出这个问题的人是非常,非常聪明还是非常,非常愚蠢。而且此代码可能声称要教给或测验您的“教训”,即一元加号运算符没有做太多事情,当然还不足以应对这种颠覆性的误导。

该代码有两个令人困惑的方面,一个奇怪的条件:

while(+(+k--)!=0)

及其控制的痴呆语句:

k=k++;

我将首先介绍第二部分。

如果您有一个k想要将其递增1 的变量,C不会给您一个,两个,三个,而是四个不同的方法:

  1. k = k + 1
  2. k += 1
  3. ++k
  4. k++

尽管有这么多赏金(或也许是因为赏金),但一些程序员还是感到困惑和咳嗽,例如

k = k++;

如果您不知道该怎么办,请不要担心:没有人可以。此表达式包含两种不同的尝试来更改k的值(k =部分和k++部分),并且由于C中没有规则说哪个尝试的修改“获胜”,因此像这样的表达式在形式上是不确定的,不仅意味着它没有定义的含义,但是包含它的整个程序都是可疑的。

现在,如果您非常仔细地看,您会发现在此特定程序中,该行k = k++实际上并未执行,因为(正如我们将要看到的)控制条件最初是假的,因此循环运行0次。因此,该特定程序实际上可能未定义-但在病理上仍然令人困惑。

另请参见这些关于此类未定义行为的所有问题的规范SO答案

但是您没有询问这k=k++部分。您询问了第一个令人困惑的部分,即+(+k--)!=0条件。这看起来很奇怪,因为这奇怪的。没有人会在真正的程序中编写过这样的代码。因此,没有理由学习如何理解它。(是的,确实如此,探索系统的边界可以帮助您了解系统的优缺点,但是我的书在富有想象力的,发人深省的探索与笨拙的,有辱骂性的探索之间有一个非常清晰的界线,这种表达方式在该行的错误一侧。)

无论如何,让我们检查一下+(+k--)!=0。(然后,让我们忘记所有这些。)必须从内而外地理解这样的表达式。我想你知道

k--

做。它采用k的当前值并将其“返回”到表达式的其余部分,并且它或多或少同时减少k,即,将数量存储k-1k

但是那怎么+办?这是一元加号,而不是二进制加号。就像一元减。您知道二进制减号会减去:

a - b

从a减去b。而且您知道一元减法会否定事物:

-a

给你a的负数。一进制+所做的是...基本上什么也没有。 在将正值更改为正值并将负值更改为负值后,可以+a提供您a的价值。所以表达

+k--

给你的东西k--给了你,那就是它k的旧价值。

但是我们还没有完成,因为我们有

+(+k--)

这仅需要+k--给您的一切,然后+再对它应用一元。所以它给了你什么,+k--给了你什么,什么k--给了你,那是k旧的价值。

所以最后,条件

while(+(+k--)!=0)

与更普通的情况完全一样

while(k-- != 0)

会做。(它的作用与看起来更复杂的条件相同while(+(+(+(+k--)))!=0)。并且括号实际上不是必需的;它也与应做的事情相同while(+ + + +k--!=0)。)

甚至弄清楚什么是“正常”情况

while(k-- != 0)

确实有点棘手。此循环中发生了两件事:由于该循环可能多次运行,因此我们将:

  1. 不断做下去k--,使自己k越来越小,而且
  2. 继续做循环的主体,无论做什么。

但是k--,在决定是否再进行一次循环之前(或正在进行),我们会立即进行操作。并记住,在递减之前,k--“返回”的旧值k。在此程序中,初始值为k0。因此k--将“返回”旧值0,然后更新k为-1。但是剩下的条件是!= 0-但是,正如我们刚刚看到的,第一次测试条件时,我们得到了0。因此,我们不会在循环中进行任何行程,因此,我们不会尝试执行有问题的陈述k=k++

换句话说,在这个特定的循环中,尽管我说“正在发生两件事”,但事实证明,事情1发生了一次,而事情2发生了零次。

无论如何,我希望现在已经足够清楚了,为什么这个糟糕的借口导致程序最终将-1打印为k。通常,我不喜欢回答这样的测验问题-感觉像在作弊-但是在这种情况下,由于我非常反对练习的全部内容,因此我不介意。


1
在任何基础课程中,典型的示例都是典型的。还有人会在考试中对操作员优先级写一个简洁的问题吗?我教数学,如果每次学生问“我什么时候才能在现实生活中做到这一点”,我是否都能
斯科特,

4
@Scott人为,然后人为。假设您是一名驾驶员培训讲师。假设您要求您的学生开车穿过繁华的市区,同时用棒球棒殴打他的头部。可以说,学生将学习一种潜在有用的技能。但是我称这种虐待。我支持我的观点,该问题中的代码具有滥用性,具有负面的教学价值。
史蒂夫·萨米特

1
“我支持我的观点”,这很公平,但是此处的关键字是“意见”。StackOverflow答案可能不是表达自己观点的最佳场所。:)
斯科特(Scott)

1
为记录起见,自该答案被接受并投票以来,我对其进行了相当大的编辑。不过,它仍然说基本相同的内容。
史蒂夫·萨米特

11

乍看起来,这段代码会调用未定义的行为,但事实并非如此。

首先,让我们正确设置代码格式:

#include<stdio.h>

int main(){
    int k = 0;
    while(+(+k--)!=0)
        k=k++;
    printf("%d\n", k);  
    return 0;
}

现在,我们可以看到该语句k=k++;位于循环内部。

现在让我们跟踪程序:

首次评估循环条件时,k其值为0。该表达式k--当前值为k,该值为0,并且k由于副作用而递减。因此,在此语句之后,值k是-1。

+表达式的前导对值没有影响,因此将其+k--评估为0并类似地+(+k--)评估为0。

然后!=评估操作员。由于0!=0为false,因此不会输入循环的主体。如果输入了正文,则将调用未定义的行为,因为k=k++读取和写入都k没有序列点。但是没有进入循环,所以没有UB。

最后,k打印的值为-1。


1
这是一个开放,存在,哲学,不可思议的问题,即未执行的未定义行为是否不是未定义的。它不是未定义的吗?我不这么认为。
史蒂夫·萨米特

2
@SteveSummit绝对没有关于它的存在性,哲学性和不可思议的东西。if (x != NULL) *x = 42;这是何时未定义x == NULL?当然不是。未执行的代码部分不会发生未定义的行为。行为这个词是一个提示。未执行的代码没有行为,未定义或其他行为。
n。代词

1
@SteveSummit如果未执行的未定义行为会使整个程序无效,则没有C程序会定义行为。因为C程序始终只是远离执行UB的循环/ if条件。以一个简单的数组迭代为例:迭代直到绑定检查失败。执行下一个循环迭代将是未定义的行为,但是因为从未执行过,所以一切都很好。
cmaster-恢复莫妮卡

3
我不会为此争论,因为我不做语言律师。但是我敢打赌,如果您问了这个问题并将其标记为“语言律师”,则可能会引发一场激烈的辩论。请注意,它k=k++在质量上与有所不同*x=42。如果x是有效的指针,则后者是明确定义的,但是无论如何,前者都是未定义的。(我承认您可能是对的,但是我还是不会再对此争论,并且我越来越担心我们已经被精通了。)
Steve Summit

1
“如果x是有效的指针,则后者是明确定义的,但是无论如何,前者都是不确定的”。仅整个程序可能具有未定义的行为。此概念不适用于孤立地获取的代码片段。
n。代词

3

这是显示操作符优先级的版本:

+(+(k--))

这两个一元运算+符不执行任何操作,因此此表达式完全等效于k--。写这篇文章的人极有可能想弄乱你的思想。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.