为什么*p++ = *p - a 给出奇怪的结果?
在处理大型数组时,我正在进行不安全的指针计算,如下所示:
*c++ = *a++ - *b++;
它按预期工作。但对于就地操作,我还需要右侧的 c 指针:
[STAThread]
unsafe static void Main(string[] args) {
double[] arr = new double[] { 2, 4, 6, 8, 10 };
double scalar = 1;
fixed (double* arrP = arr) {
double* end = arrP + arr.Length;
double* p = arrP;
double* p2 = arrP;
while (p < end) {
// gives: 3,5,7,9,2,4827634676971E+209
*p++ = *p - scalar;
// gives correct result: 1,3,5,7,9
//*p = *p - scalar;
//p++;
}
}
Console.WriteLine(String.Join<double>(",", arr));
Console.ReadKey();
}
在取消引用发生之前,指针会递增。根据优先规则(++ 在 * 之前),这是正确的。但现在新值被写入增量地址,而不是原始地址。为什么会这样呢?
我发现了这个问题:在一个语句中取消引用和前进指针? 。但它只处理右侧的 *c++ 表达式。为什么写访问与读访问不同?
此外,非常感谢 C# 规范中指针类型的优先规则的链接。到目前为止还找不到他们。
@编辑:请注意,我们在这里讨论的是 C#,而不是 C 或 C++。即使我预计这里的差异不会太大。另外,就像上面的示例一样,我知道,可以通过增加下一个代码行中的指针来防止该问题。我想知道,为什么这种行为无论如何都会如此。
While working with large arrays, I am doing unsafe pointer computations like the following:
*c++ = *a++ - *b++;
It works as expected. But for inplace operations, I need the c pointer on the right side as well:
[STAThread]
unsafe static void Main(string[] args) {
double[] arr = new double[] { 2, 4, 6, 8, 10 };
double scalar = 1;
fixed (double* arrP = arr) {
double* end = arrP + arr.Length;
double* p = arrP;
double* p2 = arrP;
while (p < end) {
// gives: 3,5,7,9,2,4827634676971E+209
*p++ = *p - scalar;
// gives correct result: 1,3,5,7,9
//*p = *p - scalar;
//p++;
}
}
Console.WriteLine(String.Join<double>(",", arr));
Console.ReadKey();
}
The pointer gets incremented before the dereference happens. This is correct according to precedence rules (++ before *). But now the new value is written to the incremented address, not to the original. Why is this so?
I found this SO question: dereference and advance pointer in one statement? . But it handles the *c++ expression on the right side only. Why would the write access be different from the read access?
Also, a link to the preceedence rules for pointer types in the C# spec would be highly appreciated. Couldn't find them so far.
@EDIT: Please note, we are talking about C# here, not C or C++. Even if I expect the difference to be not too big here. Also, like in the example above, I know, the problem can be prevented by incrementing the pointer in the next code line. I want to know, why the behaviour is as described anyway.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
关键就在这里这句话:
这意味着您认为副作用的优先级和顺序是相关的。 他们不是。副作用按照从左到右的顺序发生,故事结束。如果有
,那么乘法会在加法之前发生,因为乘法的优先级更高。并且加法发生在赋值之前,因为加法的优先级更高。 A()、B()、C() 和 D() 的副作用按从左到右的顺序发生,与运算符的优先级无关。执行顺序不相关< /em> 优先。 (如果由于处理器缓存问题而从另一个线程进行观察,则可能会观察到副作用以不同的顺序发生,但一个线程中的副作用始终按从左到右的顺序观察到。)
在您的示例中,
p++
位于右侧*p
的左侧,因此发生p++
的副作用之前观察右边的副作用。更具体地说,变量的赋值运算符的操作是:——计算左边变量的地址——是 ++ 的作用。
这在 C# 规范中有明确定义;有关详细信息,请参阅有关运算符优先级和执行顺序的部分。
如果您对这个主题感兴趣,请参阅我关于优先级、关联性和顺序之间差异的详细信息的大量文章:
http://blogs.msdn.com/b/ericlippert/archive/tags/precedence/
如果您不明白 ++ 是如何工作的——不幸的是,几乎没有人明白—— - 请参阅此问题:
之间有什么区别i++ 和 ++i?
如果您不明白赋值是如何工作的——令我惊讶的是,几乎没有人理解赋值是如何工作的——请参阅:
http://blogs.msdn.com/b/ericlippert/archive /tags/simple+assignment/
http:// blogs.msdn.com/b/ericlippert/archive/tags/compound+assignment/
其他答案指出,在 C 和 C++ 编程语言中,语言规范没有指定副作用出现的顺序。如果副作用及其观察结果位于同一个“序列点”内,就像这里一样,就会发生这种情况。在 C 中,允许 ++ 的副作用在语句结束之前的任何时间发生。赋值之后、赋值之前、何时,由编译器决定。 C# 不允许这种自由度。在 C# 中,在执行右侧代码时,会观察到左侧的副作用已经发生。
您想要的规范部分是 18.5,其中规定:
因此,请阅读语法并解决它。首先阅读附录 B 第 3 节中的语法。
The key is this sentence right here:
The implication is that you believe that precedence and order of side effects are related. They are not. Side effects happen in order left to right, period, end of story. If you have
then the multiplication happens before the addition, because multiplication is higher precedence. And the addition happens before the assignment because addition is higher precedence. The side effects of A(), B(), C() and D() happen in left-to-right order irrespective of precedence of the operators. Order of execution is unrelated to precedence. (Side effects may be observed to happen in a different order if you are observing from another thread due to processor cache issues, but side effects in one thread are always observed in left-to-right order.)
In your example the
p++
is to the left of the right-hand side*p
, and therefore the side effect of thep++
happens before the observation of the side effect on the right. More specifically, the operation of the assignment operator to a variable is:The first step -- evaluate the address of the variable on the left -- is what does the ++.
This is clearly defined in the C# spec; see the section on operator precedence and order of execution for details.
If this subject interests you, see my numerous articles on the details of the difference between precedence, associativity and order:
http://blogs.msdn.com/b/ericlippert/archive/tags/precedence/
If you don't understand how ++ works -- and almost no one does, unfortunately -- see this question:
What is the difference between i++ and ++i?
If you don't understand how assignment works -- and it has been surprising to me to learn that almost no one does understand how assignment works -- see:
http://blogs.msdn.com/b/ericlippert/archive/tags/simple+assignment/
http://blogs.msdn.com/b/ericlippert/archive/tags/compound+assignment/
The other answers are pointing out that in the C and C++ programming languages, the language specifications do not specify in what order side effects appear to happen if the side effect and its observation are within the same "sequence point", as they are here. In C it is permissible for the side effect of the ++ to happen at any time before the end of the statement. After the assignment, before the assignment, whenever, at the discretion of the compiler. C# does not permit that sort of lattitude. In C#, a side effect to the left is observed to have happened by the time that code to the right executes.
The spec section you want is 18.5, which states:
So read the grammar and work it out. Start by reading the grammar in Appendix B, section 3.