在块作用域中的静态变量的规则 (与之相对的是全局作用域的静态变? ? E序W一ơ执行到他的声明的时候进行初始化.

察看下面的竞争条?

int ComputeSomething()
{
   static int cachedResult = ComputeSomethingSlowly();
   return cachedResult;
}

q段代码的意图是在该函数W一ơ被调用的时候去计算一些费? q且把结果缓冲v来待函数来再被调用的时候则直接q回q个值即?

q个基本技巧的变种,在网l上也被叫做 避免 "static initialization order fiasco". ( fiascoq个?在这个网上有非常棒的描q?因此我徏议大家去Mȝ后去理解?)

q段代码的问题是非线E安全的. 在局部作用域中的静态变量是~译时会在编译器内部转换成下面的样子:

int ComputeSomething()
{
  static bool cachedResult_computed = false;
  static int cachedResult;
  if (!cachedResult_computed) {
     cachedResult_computed = true;
     cachedResult = ComputeSomethingSlowly();
  }
  return cachedResult;
}

现在竞争条g比较容易看C.

假设两个U程在同一时刻都调用这个函? W一个线E在执行 cachedResult_computed = true ? 被抢? W二个线E现在看到的 cachedResult_computed 是一个真? true ),然后qq了if分支的处?最后该函数q回的是一个未初始化的变量.

现在你看到的东西q不是一个编译器的bug, q个行ؓ C++ 标准所要求?

你也能写一个变体来产生一个更p糕的问?

class Something { ... };
int ComputeSomething()
{
   static Something s;
   return s.ComputeIt();
}

同样的在~译器内部它会被重写 (q次, 我们使用C++伪代?:

class Something { ... };
int ComputeSomething()
{
  static bool s_constructed = false;
  static uninitialized Something s;
  if (!s_constructed) {
      s_constructed = true;
      new(&s) Something; // construct it
      atexit(DestructS);
  }
  return s.ComputeIt();
}
// Destruct s at process termination
void DestructS()
{
   ComputeSomething::s.~Something();
}

注意q里有多重的竞争条g. 像前面所说的, 一个线E很可能在另一个线E之前运行ƈ且在"s"q没有被构造前׃用它.

甚至更糟p的情况, W一个线E很可能在s_contructed 条g判定 之后,在他被设|成"true"之前被抢? 在这U场合下, 对象s׃?span style="font-weight: bold;">双重构?/span>?span style="font-weight: bold;">双重析构. 

q样׃是很?

但是{等, qƈ不是全部, 现在(原文是Not,我认为是Now的笔?看看如果?span style="font-style: italic;">两个q行期初始化局部静态变量的话会发生什?

class Something { ... };
int ComputeSomething()
{
static Something s(0);
static Something t(1);
return s.ComputeIt() + t.ComputeIt();
}

上面的代码会被编译器转化Z面的伪C++代码:

class Something { ... };
int ComputeSomething()
{
  static char constructed = 0;
static uninitialized Something s;
if (!(constructed & 1)) {
constructed |= 1;
new(&s) Something; // construct it
atexit(DestructS);
}
static uninitialized Something t;
if (!(constructed & 2)) {
constructed |= 2;
new(&t) Something; // construct it
atexit(DestructT);
}
return s.ComputeIt() + t.ComputeIt();
}

Z节省I间, ~译器会把两?x_constructed" 变量攑ֈ一?bitfield ? 现在q里在变?construted"上就有多?span style="font-weight: bold;">无内部锁?/span>的读-?存操?

现在考虑一下如果一个线E尝试去执行 "constructed |= 1", 而在同一旉另一个线E尝试执?"constructed |= 2".

在x86q_? q条语句会被汇编?/p>

  or constructed, 1
...
or constructed, 2
q没?"lock" 前缀. 在多处理机器? 很有可能发生两个存储都去d一个旧值ƈ且互怋用冲H的D行碰?clobber).

?ia64 ?alphaq_? q个撞更加明?因ؓ它们么没有这L???/span>的单条指? 而是被编码成三条指o:

  ldl t1,0(a0)     ; load
addl t1,1,t1     ; modify
stl t1,1,0(a0)   ; store

如果q个U程?load ?store之间被抢? q个存储的值可能将不再是它曄要写入的那个?

因此,现在考虑下面q个有问题的执行序:

  • U程A 在测?"constructed" 条g后发C是零, q且正要准备把这个D定成1Q?但是它被抢占?
  • U程B q入同样的函? 看到 "constructed" 是零ql去构?"s" ?"t", d?"constructed" {于3.
  • U程A l箋执行q且完成它的 ???的指令序? 讑֮ "constructed" ?1, 然后构?"s" (W二?.
  • U程A 然后l箋L?"t" (W二? q设?"constructed" (最l? ?3.

现在, 你可能会认ؓ你能用界区 (critical section) 来封装这个运行期初始化动?

int ComputeSomething()
{
EnterCriticalSection(...);
static int cachedResult = ComputeSomethingSlowly();
LeaveCriticalSection(...);
return cachedResult;
}

因ؓ你现在把q个一ơ初始化攑ֈ了界区里面,而它线E安?

但是如果从同一个线E再一ơ调用这个函C怎样? ("我们跟踪了这个调? 它确实是来自q个U程!") 如果 ComputeSomethingSlowly() 它自己间接地调用 ComputeSomething()׃发生q个状况.

l论: 当你看见一个局部静态变量在q行期初始化? 你一定要心.