我有以下代码.
#include <iostream>
int * foo()
{
int a = 5;
return &a;
}
int main()
{
int* p = foo();
std::cout << *p;
*p = 8;
std::cout << *p;
}
Run Code Online (Sandbox Code Playgroud)
而代码只是运行而没有运行时异常!
输出是 58
怎么会这样?本地变量的内存不能在其功能之外无法访问吗?
据我所知,C#是一种安全的语言,除了通过unsafe关键字之外,不允许访问未分配的内存.但是,当线程之间存在不同步的访问时,其内存模型允许重新排序.这会导致竞争危险,在实例完全初始化之前,对新实例的引用似乎可用于竞赛线程,并且是双重检查锁定的众所周知的问题.Chris Brumme(来自CLR团队)在他们的Memory Model文章中解释了这一点:
考虑标准的双锁协议:
if (a == null)
{
lock(obj)
{
if (a == null)
a = new A();
}
}
Run Code Online (Sandbox Code Playgroud)
这是在典型情况下避免锁定读取'a'的常用技术.它在X86上运行得很好.但它将被ECMA CLI规范的合法但薄弱的实施所打破.确实,根据ECMA规范,获取锁具有获取语义并释放锁具有释放语义.
但是,我们必须假设在建造'a'期间已经发生了一系列商店.这些商店可以任意重新排序,包括将它们推迟到将新对象分配给'a'的出版商店之后的可能性.那时,在store.release之前有一个小窗口离开锁.在该窗口内,其他CPU可以在引用'a'中导航并查看部分构造的实例.
我一直对"部分构造的实例"的含义感到困惑.假设.NET运行时在分配而不是垃圾收集(讨论)时清除内存,这是否意味着另一个线程可能会读取仍包含垃圾收集对象数据的内存(如不安全语言中发生的情况)?
考虑以下具体示例:
byte[] buffer = new byte[2];
Parallel.Invoke(
() => buffer = new byte[4],
() => Console.WriteLine(BitConverter.ToString(buffer)));
Run Code Online (Sandbox Code Playgroud)
以上是竞争条件; 输出将是00-00或00-00-00-00.但是,第二个线程是否有可能在数组的内存初始化为0 buffer 之前读取新引用,并输出一些其他任意字符串?