我们班的C编程教授问了这个问题:
您将获得代码:
int x=1;
printf("%d",++x,x+1);
它总会产生什么输出?
大多数学生说未定义的行为。任何人都可以帮助我理解为什么会这样吗?
感谢编辑和答案,但我仍感到困惑。
答案 0 :(得分:40)
在每种合理的情况下,输出可能为2。实际上,你拥有的是未定义的行为。
具体来说,标准说:
在前一个和下一个序列点之间,对象的存储值最多只能通过表达式的计算修改一次。此外,先前的值应该是只读的,以确定要存储的值。
在评估函数的参数之前有一个序列点,并且在评估了所有参数之后的序列点(但是尚未调用的函数)。在这两者之间(即,在评估参数时),不一个序列点(除非参数是一个表达式在内部包含一个,例如使用&&
{{1} }或||
运营商)。
这意味着对,
的调用正在读取先前值两者以确定存储的值(即printf
)和确定第二个参数的值(即++x
)。这显然违反了上述要求,导致行为不明确。
您提供了一个没有给出转换说明符的额外参数的事实 not 会导致未定义的行为。如果提供转换说明符的更少的参数,或如果参数的(提升的)类型与转换说明符的类型不一致,则会得到未定义的行为 - 但是传递额外的行为参数不。
答案 1 :(得分:14)
任何时候程序的行为都是未定义的,任何事情都可能发生 - 经典的短语是“恶魔可能会飞出你的鼻子” - 尽管大多数实现都没有那个远。 / p>
函数的参数在概念上是并行评估的(技术术语是它们的评估之间没有序列点)。这意味着表达式++x
和x+1
可以按此顺序进行评估,顺序相反,或以某种交错方式。修改变量并尝试并行访问其值时,行为未定义。
对于许多实现,参数是按顺序计算的(尽管并不总是从左到右)。所以你不太可能在现实世界中看到任何东西。
但是,编译器可以生成如下代码:
r1
。 x+1
添加1来计算r1
。++x
添加1来计算r1
。这没关系,因为x
已加载到r1
。鉴于编译器的设计方式,步骤2不能修改r1
,因为只有在读取x
以及在两个序列点之间写入时才会发生这种情况。这是C标准所禁止的。r1
存储到x
。在这个(假设但正确的)编译器上,程序将打印3。
(编辑:将额外参数传递给printf
是正确的(N1256中的§7.19.6.1-2;感谢Prasoon Saurav)指向此另外:添加了一个例子。)
答案 2 :(得分:11)
正确答案是:代码产生未定义的行为。
行为未定义的原因是两个表达式++x
和x + 1
正在修改x
并阅读x
以获取无关(修改)原因这两个动作不是由序列点分隔的。这导致C(和C ++)中的未定义行为。要求以C语言标准的6.5 / 2给出。
注意,在这种情况下,未定义的行为与printf
函数只给出一个格式说明符和两个实际参数这一事实完全无关。为printf
提供更多参数,而不是格式字符串中的格式说明符在C语言中是完全合法的。同样,问题的根源在于违反C语言的表达式评估要求。
另请注意,本次讨论的一些参与者未能理解未定义行为的概念,并坚持将其与未指定行为的概念混合在一起。为了更好地说明差异,让我们考虑以下简单的例子
int inc_x(int *x) { return ++*x; }
int x_plus_1(int x) { return x + 1; }
int x = 1;
printf("%d", inc_x(&x), x_plus_1(x));
上述代码与原始代码“等效”,只是涉及我们x
的操作包含在函数中。在这个最新的例子中会发生什么?
此代码中没有未定义的行为。但由于printf
参数的评估顺序是未指定,因此此代码会生成未指定的行为,即printf
可能会被调用为printf("%d", 2, 2)
或printf("%d", 2, 3)
。在这两种情况下,输出确实是2
。但是,此变体的重要区别在于,对x
的所有访问都包含在每个函数的开头和结尾处的序列点中,因此此变体不会产生未定义的行为。
这正是其他一些海报试图强迫原始例子的原因。但它无法完成。原始示例生成 undefined 行为,这是一个完全不同的野兽。他们显然试图坚持在实践中未定义的行为总是等同于未指明的行为。这是一个完全虚假的主张,只表明制造它的人缺乏专业知识。原始代码产生未定义的行为,句点。
要继续该示例,我们将以前的代码示例修改为
printf("%d %d", inc_x(&x), x_plus_1(x));
代码的输出通常是不可预测的。它可以打印2 2
,也可以打印2 3
。但请注意,即使行为不可预测,它仍然不会产生未定义的行为。行为是未指定,位不是未定义。未指定的行为仅限于两种可能:2 2
或2 3
。未定义的行为不限于任何内容。它可以格式化硬盘而不是打印东西。感受不同。
答案 3 :(得分:2)
大多数学生说未定义的行为。任何人都可以帮助我理解为什么会这样吗?
因为未指定计算函数参数的顺序。
答案 4 :(得分:2)
它总会产生什么输出?
它会在我能想到的所有环境中产生2。但是,严格解释C99标准会导致行为未定义,因为对x的访问不符合序列点之间存在的要求。
大多数学生说未定义的行为。 任何人都可以帮助我理解它为什么 是这样吗?
我现在将讨论第二个问题,我理解为“为什么我班上的大多数学生都说所显示的代码构成了未定义的行为?”我认为到目前为止还没有其他海报回答。学生中的一部分将记住未定义的表达式值的示例,如
f(++i,i)
你给出的代码符合这种模式,但学生们错误地认为行为是定义的,因为printf忽略了最后一个参数。这种细微差别使许多学生感到困惑。学生的另一部分将像大卫·索恩利一样精通标准,并且出于上述正确理由说出“未定义的行为”。
答案 5 :(得分:1)
关于未定义行为的观点是正确的,但还有一个问题:printf可能会失败。它在做文件IO;它有多种原因可能会失败,如果不知道完整的程序及其执行的上下文,就不可能消除它们。
答案 6 :(得分:0)
回应codaddict答案是2。
将使用参数2调用printf,它将打印出来。
如果此代码放在如下的上下文中:
void do_something()
{
int x=1;
printf("%d",++x,x+1);
}
然后完全无误地定义该函数的行为。我当然不是认为这是好的或正确的,或者之后x的值是可以确定的。
答案 7 :(得分:-1)
输出将始终为(对于最重要的标准兼容编译器和系统的99.98%)2。
根据标准,这似乎是,按照定义,“未定义的行为”,一个自我辩解的定义/答案,并且没有说明实际可能发生的事情,尤其是<强>为什么强>
实用程序夹板(这不是std合规性检查工具),以及splint的程序员,将其视为“未指定的行为”。这基本上意味着(x+1)
的评估可以给出1 + 1或2 + 1,具体取决于实际完成x
的更新的时间。因为表达式被丢弃(printf格式读取1个参数),输出不受影响,我们仍然可以说它是2。
undefined.c:7:20:参数2修改x,由参数3使用(顺序为 实际参数的评估未定义):printf(“%d \ n”,++ x,x + 1) 代码具有未指定的行为。功能参数的评估顺序或 未定义子表达式,因此如果使用并修改了一个值 不受序列点约束评估分隔的不同地方 顺序,那么表达式的结果是未指定的。
如前所述,未指明的行为仅影响(x+1)
的评估,而不影响整个语句或其他表达式。因此,在“未指定的行为”的情况下,我们可以说输出为2,没有人可以反对。
但这不是未指明的行为,它似乎是“未定义的行为”。并且“未定义的行为”似乎必须影响整个语句而不是单个表达式。这是由于实际发生“未定义行为”的地方(即究竟影响到什么)的误解。
如果有附加“未定义行为”的动机仅仅是(x+1)
表达式,就像在“未指定的行为”情况下一样,那么我们仍然可以说输出是总是(100%)2。仅将“未定义的行为”附加到(x+1)
意味着我们无法说明它是1 + 1还是2 + 1;它只是“任何东西”。但同样,由于printf,“任何东西”都被删除了,这意味着答案将是“总是(100%)2”。
相反,由于不稳定的不对称性,“未定义的行为”不能附加仅仅x+1
,但实际上必须至少影响 ++x
(顺便说一下,它是负责未定义的行为),如果不是整个语句。如果它只感染++x
表达式,则输出是“未定义的值”,即任何整数,例如如果它感染整个语句,那么你可以在控制台输出中看到gargabe,可能你可能必须用ctrl-c停止程序,可能在它开始扼杀你的cpu之前。
根据一个城市传说,“未定义的行为”不仅会感染整个程序,还会感染您的计算机和物理定律,因此您的程序可以创建神奇的生物并飞走或吃掉您。
没有答案可以解释有关该主题的任何内容。它们只是一个“哦看标准说这个”(这只是一个解释,像往常一样!)。所以至少你已经学会了“标准存在”,并且他们提出了教育问题(当然,不要忘记你的代码错误,无论未定义/未指明的行为主义和其他标准事实),无用的逻辑论证和漫无目的的深入调查和理解。