这段代码定义明确吗?
此代码取自此处的讨论。
someInstance.Fun(++k).Gun(10).Sun(k).Tun();
这段代码定义明确吗? Fun() 中的 ++k
是否先于 Sun() 中的 k
计算?
如果 k
是用户定义类型而不是内置类型怎么办?上面的函数调用顺序在哪些方面与此不同:
eat(++k);drink(10);sleep(k);
据我所知,在这两种情况下,每个函数调用之后都存在一个序列点。如果是这样,那么为什么第一种情况不能像第二种情况一样被明确定义呢?
C++ ISO 标准的第 1.9.17 节对序列点和函数求值进行了如下说明:
当调用函数时(无论是 不是内联函数),有 评估后的序列点 所有函数参数(如果有) 这发生在执行之前 中的任何表达或陈述 函数体。还有一个 复制后的序列点 返回值和之前 执行外部的任何表达式 函数。
This code is taken from a discussion going on here.
someInstance.Fun(++k).Gun(10).Sun(k).Tun();
Is this code well-defined? Is ++k
in Fun() evaluated before k
in Sun()?
What if k
is user-defined type, not built-in type? And in what ways the above function calls order is different from this:
eat(++k);drink(10);sleep(k);
As far as I know, in both situations, there exists a sequence point after each function call. If so, then why can't the first case is also well-defined like the second one?
Section 1.9.17 of the C++ ISO standard says this about sequence points and function evaluation:
When calling a function (whether or
not the function is inline), there is
a sequence point after the evaluation
of all function arguments (if any)
which takes place before execution of
any expressions or statements in the
function body. There is also a
sequence point after the copying of a
returned value and before the
execution of any expressions outside
the function.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
我认为,如果您准确阅读该标准引用的内容,第一种情况将无法明确定义:
这告诉我们的不是“在对函数的参数求值之后唯一可能发生的事情是实际的函数调用”,但简单地说,在参数求值完成之后、函数调用之前的某个时刻存在一个序列点。
但如果你想象这样的情况:
这给我们的唯一保证是:
X
在调用foo
之前评估,并且Y
在调用bar
之前进行评估。但这样的顺序仍然是可能的:
X
Y
X
与foo
分开的序列点call)foo
Y
与bar
分开的序列点 call)bar
当然,我们也可以交换前两项,在
X
之前评估Y
。为什么不呢?该标准仅要求在函数体的第一个语句之前对函数的参数进行完全评估,并且上述序列满足该要求。至少这是我的解释。似乎并没有说参数求值和函数体之间不会发生任何其他事情 - 只是这两者被序列点分隔开。
I think if you read exactly what that standard quote says, the first case won't be well-defined:
What this tells us is not that "the only thing that can happen after the arguments for a function have been evaluated is the actual function call", but simply that there is a sequence point at some point after the evaluation of arguments finishes, and before the function call.
But if you imagine a case like this:
the only guarantee this gives us is that:
X
is evaluated before the call tofoo
, andY
is evaluated before the call tobar
.But an order such as this would still be possible:
X
Y
X
fromfoo
call)foo
Y
frombar
call)bar
and of course, we could also swap around the first two items, evaluating
Y
beforeX
. Why not? The standard only requires that the arguments for a function are fully evaluated before the first statement of the function body, and the above sequences satisfy that requirement.That's my interpretation, at least. It doesn't seem to say that nothing else may occur between argument evaluation and function body -- just that those two are separated by a sequence point.
这取决于
Sun
的定义方式。以下是明确定义的,如果将
Sun
的参数类型更改为int
,则变为未定义。让我们绘制一棵采用int< 的版本树
/代码>。
可以看出,我们读取了
k
(由V(k)
指定),并且对k
产生了副作用(在最上面)不被序列点分隔:在此表达式中,相对于其他子表达式,根本不存在序列点。最底部的%
表示完整表达序列点。This depends on how
Sun
is defined. The following is well-definedIf you change the parameter type of
Sun
toint
, it becomes undefined. Let's draw a tree of the version taking anint
.As can be seen, we have a read of
k
(designated byV(k)
) and a side-effect onk
(at the very top) that are not separated by a sequence point: In this expression, relative to each other sub-expression, there is no sequence point at all. The very bottom%
signifies the full-expression sequence point.这是未定义的行为,因为 k 的值在同一表达式中被修改和读取,而没有中间序列点。请参阅这个问题的精彩长答案。
1.9.17 的引用告诉您,所有函数参数在调用函数体之前都会被求值,但没有说明同一表达式中不同函数调用的参数求值的相对顺序 - 不能保证“++k Fun() 在 Sun() 中的 k 之前计算”。
有所不同,因为
;
是一个序列点,因此评估的顺序是明确定义的。This is undefined behavior, because the value of k is being both modified and read in the same expression, without an intervening sequence point. See the excellent long answer to this question.
The quote from 1.9.17 tells you that all function arguments are evaluated before the body of the function is called, but doesn't say anything about the relative order of evaluation of arguments to different function calls within the same expression -- no guarantee that "++k Fun() is evaluated before k in Sun()".
is different because the
;
is a sequence point, so the order of evaluation is well-defined.作为一个小测试,请考虑:
我使用 gcc 3.4.6 运行它,没有优化,并得到:
...with -O3...
因此,该版本的 3.4.6 有一个主要错误(这有点难以解决)相信),或者如菲利普·波特所建议的那样,序列未定义。 (带/不带 -O3 的 GCC 4.1.1 生成了 5, 5, 5, 5。)
编辑 - 我在下面的评论中对讨论的总结:
As a little test, consider:
I run this with gcc 3.4.6 and no optimisation and get:
...with -O3...
So, either that version of 3.4.6 had a major bug (which is a bit hard to believe), or the sequence is undefined as Philip Potter suggested. (GCC 4.1.1 with/without -O3 produced 5, 5, 5, 5.)
EDIT - my summary of the discussion in comments below:
我知道编译器的行为不能真正证明任何事情,但我认为检查编译器的内部表示会给出什么会很有趣(仍然比汇编检查更高级别)。
我已将 Clang/LLVM 在线演示 与此代码一起使用:
并使用标准优化进行编译(在 C++ 模式下),它给出:
,我确实觉得有趣(是否有任何其他编译器对此发出警告?Comeau在线没有)
顺便说一句,它还生成了以下中间表示(向右滚动):
显然,Clang 的行为类似于 gcc 4.xx,并且在执行任何函数调用之前首先评估所有参数。
I know that the behavior of compilers cannot really prove anything, but I thought it would be interesting to check out what the internal representation of a compiler would give (still a bit higher level than assembly inspection).
I've used the Clang/LLVM online demo with this code:
And compiled with the standard optimizations (in C++ mode), it gave:
which I did find interesting (did any other compiler warned about this ? Comeau online did not)
As an aside it also produced the following Intermediate Representation (scroll to the right):
Apparently, Clang behaves like gcc 4.x.x does and first evaluates all arguments before performing any function call.
一个有点老的问题,但这使它更有趣,因为正确的答案是:
这取决于!
当这个问题最初于 2011 年提出时,答案显然是否定的!
someInstance.Fun(++k).Gun(10).Sun(k).Tun();
是未指定的行为,导致变量k
被修改并读取同样的表情。但在 C++17 中,情况发生了变化:
https://wg21.link/p0145
这样,函数的参数计算被指定为在任何进一步的链式函数调用之前进行排序。
这意味着现在
someInstance.Fun(++k).Gun(10).Sun(k).Tun();
计算顺序是明确定义且明确的:someInstance>> ++k>>乐趣()>> 10>>枪()>> k>>太阳()>>敦()
A bit older question but this makes it more interesting cause the correct answer is:
It depends!
When the question was originally asked in 2011 the answer was clearly NO!
someInstance.Fun(++k).Gun(10).Sun(k).Tun();
is unspecified behaviour cause the variablek
is modified an read in the same expression.But with C++17 this was changed:
https://wg21.link/p0145
With this the argument-evaluation of a function was specified as being sequenced-before any further chained function-calls.
This means that now
someInstance.Fun(++k).Gun(10).Sun(k).Tun();
the evaluation-order is well-defined and unambiguous:someInstance >> ++k >> Fun() >> 10 >> Gun() >> k >> Sun() >> Tun()
第二种情况当然是明确定义的。以分号结尾的标记字符串是 C++ 中的原子语句。每个语句在下一个语句开始之前都会被解析、处理和完成。
The second case is certainly well-defined. A string of tokens that ends with a semicolon is an atomic statement in C++. Each statement is parsed, processed and completed before the next statement is begun.