一条语句中的索引、赋值和增量在 C++ 中的行为不同和 C#。为什么?
为什么此代码示例在 c++ 和 C# 中表现不同。
[C++ 示例]
int arr[2];
int index = 0;
arr[index] = ++index;
结果为arr[1] = 1;
[C# 示例]
int[] arr = new int[2];
int index = 0;
arr[index] = ++index;
结果为arr[0] = 1;
我觉得这很奇怪。两种语言以不同的方式实现它肯定有一些理由吗?我想知道 C++/CLI 会输出什么?
Why is this example of code behaving differently in c++ and C#.
[C++ Example]
int arr[2];
int index = 0;
arr[index] = ++index;
The result of which will be arr[1] = 1;
[C# Example]
int[] arr = new int[2];
int index = 0;
arr[index] = ++index;
The result of which will be arr[0] = 1;
I find this very strange. Surely there must be some rationale for both languages to implement it differently? I wonder what would C++/CLI output?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
正如其他人所指出的,此代码的行为在 C/C++ 中未定义。你可以获得任何结果。
C# 代码的行为由 C# 标准严格定义。
好吧,假设您正在设计 C#,并希望让 C++ 程序员能够轻松学习该语言。您会选择复制 C++ 解决此问题的方法,即保留其未定义吗?您是否真的想让完全聪明的开发人员很容易意外地编写出编译器可以根据其想要的任何含义来弥补的代码?
C# 的设计者并不认为简单表达式的未定义行为是一件好事,因此我们严格定义了此类表达式的含义。我们不可能同意每个 C++ 编译器的做法,因为不同的 C++ 编译器会为此类代码提供不同的结果,因此我们无法同意所有这些。
至于为什么 C++ 的设计者认为最好让像这样的简单表达式具有未定义的行为,好吧,你必须问他们之一。我当然可以做出一些猜测,但这些只是有根据的猜测。
我写了很多关于此类问题的博客文章;我最近的一篇几乎与您在这里提到的代码有关。您可能想阅读的一些文章:
C# 的设计如何鼓励消除微妙的错误:
http://blogs.msdn.com/ericlippert/archive/2007/08/14/c-and-the-pit-of-despair.aspx
C# 中优先级、关联性和执行顺序之间到底有什么关系?
http://blogs.msdn .com/ericlippert/archive/2008/05/23/precedence-vs-associativity-vs-order.aspx
索引、赋值和增量的副作用按什么顺序发生?
http://blogs.msdn.com /ericlippert/archive/2009/08/10/precedence-vs-order-redux.aspx
As others have noted, the behaviour of this code is undefined in C/C++. You can get any result whatsoever.
The behaviour of your C# code is strictly defined by the C# standard.
Well, suppose you were designing C#, and wished to make the language easy for C++ programmers to learn. Would you choose to copy C++'s approach to this problem, namely, leave it undefined? Do you really want to make it easy for perfectly intelligent developers to accidentally write code that the compiler can just make up any meaning for that it wants?
The designers of C# do not believe that undefined behaviour of simple expressions is a good thing, and therefore we have strictly defined what expressions like this mean. We cannot possibly agree with what every C++ compiler does because different C++ compilers give you different results for this sort of code, and so we cannot agree with all of them.
As for why the designers of C++ believe that it is better to leave simple expressions like this to have undefined behaviour, well, you'll have to ask one of them. I could certainly make some conjectures, but those would just be educated guesses.
I've written a number of blog articles about this sort of issue; my most recent one was about almost exactly the code you mention here. Some articles you might want to read:
How the design of C# encourages elimination of subtle bugs:
http://blogs.msdn.com/ericlippert/archive/2007/08/14/c-and-the-pit-of-despair.aspx
Exactly what is the relationship between precedence, associativity, and order of execution in C#?
http://blogs.msdn.com/ericlippert/archive/2008/05/23/precedence-vs-associativity-vs-order.aspx
In what order do the side effects of indexing, assignment and increment happen?
http://blogs.msdn.com/ericlippert/archive/2009/08/10/precedence-vs-order-redux.aspx
事实上,您的 C++ 代码可以做任何事情。
arr[index] = ++index;
调用未定义的行为。Your C++ code could, in fact, do anything.
arr[index] = ++index;
invokes undefined behaviour.C++ 中未指定在同一赋值中使用索引和 ++index 的行为。你不能只是这样做:编写 arr[index] = index + 1 并在之后增加你的变量。就此而言,在我的机器上使用 C++ 编译器时,我看到 arr[0] = 1,并且 arr[1] 未受影响。
The behaviour of using index and ++index inside the same assignment is unspecified in C++. You just can't just do that: write
arr[index] = index + 1
and increment your variable after that. For that matter, with my C++ compiler on my machine I see arr[0] = 1, and arr[1] is untouched.注意:根据@Eric Lippert的答案,行为是严格的为 C# 定义,所以让我重写我的答案。
此代码:
即使 C# 编译器确切地知道如何计算它以及按什么顺序计算它,也很难阅读。仅出于这个原因就应该避免这种情况。
关于 C# 运算符的 MSDN 页面 到目前为止指出这种行为可能是未定义的,尽管埃里克指出事实并非如此。事实上,多个文档来源(不过在这一点上我相信埃里克)会有所不同,这也表明这可能是最好不要管的事情。
Note: Acording to @Eric Lippert's answer, the behavior is strictly defined for C#, so let me reword my answer on this.
This code:
Is hard to read even if the C# compiler knows exactly how to evaluate it and in which order. For this reason alone it should be avoided.
The MSDN page on C# Operators goes so far as pointing out that this behaviour might be undefined, even though Eric points out it is not. The fact that multiple sources of documentation (I'll trust Eric on this however) gets it different is also a tell that this might be something best left alone.
当您调用未定义的行为时,C++ 版本的结果并不总是如您所写。在 C++ 中,如果您在表达式中使用变量的值,并且该变量也修改了相同的表达式,那么您将得到未定义的行为,除非读取该值是确定要写入的值的一部分,或者表达式包含读取和写入之间的序列点。
在表达式中,您正在读取
index
的值来确定将=
右侧的结果分配到何处,但右侧子表达式也会修改索引
。The result of the C++ version will not always be as you write as you are invoking undefined behaviour. In C++ you will get undefined behaviour if you use the value of a variable in an expression when that variable is also modified the same expression unless reading that value is part of determining the value to be written, or the expression contains a sequence point between the read and the write.
In your expression, you are reading the value of
index
to determine where to assign the result of the right hand side of the=
, but the right hand sub-expression also modifiesindex
.至少在 C++ 的情况下,您通过预递增和使用
index
来调用未定义的行为,而中间没有序列点。如果您将该代码提供给 GCC 并启用警告,它会说:我猜它在 C# 中也是未定义的,但我不知道这种语言。至少对于 C 和 C++ 来说,答案是编译器可以做任何它想做的事情,而不会因为代码错误而出错。不同的编译器(甚至同一个编译器)也没有义务产生一致的结果。
In the case of C++, at least, you're invoking undefined behavior by preincrementing and using
index
without a sequence point in between. If you feed that code to GCC with warnings enabled it will say:I'm guessing that it's undefined as well in C#, but I don't know the language. For C and C++ at the very least though, the answer is that the compiler can do anything it wants without being wrong because your code is erroneous. There's no obligation for different compilers (or even the same compiler) to produce consistent results, either.
C# 中的索引是一种值类型,这意味着在对其执行操作时会返回该值的一个新实例。
如果您将其想象为过程而不是运算符,则该过程将如下所示:
但是,C++ 适用于对象的引用,因此该过程将如下所示:
注意:如果您已将运算符应用于对象(比如重载 ++ 运算符)那么 C# 的行为将类似于 C++,因为对象类型作为引用传递。
index in C# is a value type, which means you return a new instance of the value when you perform operations on it.
If you imagine it as a procedure instead of an operator, the procedure would look like this:
C++, however, works on the reference of the object, so the procedure would look like:
Note: if you had been applying the operator on an object (say overloaded the ++ operator) then C# would behave like C++, since object types are passed as references.