2015-12-04 176 views
3

这个问题是从编译器实现的角度来看静态变量和静态变量的区别是什么?

我想知道关于C#中的静态变量,我找到了解释他们没有实现的原因(这里:http://blogs.msdn.com/b/csharpfaq/archive/2004/05/11/why-doesn-t-c-support-static-method-variables.aspx)。

引用“有可能通过具有类级别静态来获得几乎相同的效果” - 这让我好奇,有什么不同?假设C#会使用静态变量语法 - 实现可以“静静地将其作为静态字段推送并保留条件初始化(如有必要)”。完成。

我可以发现的唯一的问题是给定初始化时值类型的问题。还有什么其他东西可以适合“接近”?

我重申了这个问题 - 如何在C#编译器中实现静态变量仅使用现有功能(因此静态变量必须在当前状态下内部生成)。

+0

我认为不同之处在于方法级别的静态只能从该方法访问,而类级别的静态可以从类中的任何位置和类之外的位置访问,如果它碰巧是公共的,虽然我猜如果您尝试以获得几乎相同的效果,你会宣布它是私人的。 – juharr

+0

@juharr,请注意“编译器实现透视图”,创建只有知道它存在的一方才能访问的隐藏类成员是微不足道的。 – greenoldman

+0

顺便说一句 - 你的问题含糊不清。也许可以澄清你真正想要问什么。 –

回答

3

它实际上很容易检查编译器在C#中实现静态变量所必须做的事情。

C#被设计为编译为CIL(通用中间语言)。支持静态变量的C++也可以编译为CIL。

让我们看看当我们做什么时会发生什么。首先,让我们看看下面的简单类:

public ref class Class1 
{ 
private: 
    static int i = 0; 

public: 
    int M() { 
     static int i = 0; 
     i++; 
     return i; 
    } 

    int M2() { 
     i++; 
     return i; 
    } 
}; 

}

两种方法,同样的行为 - i初始化为0,递增,每个方法被调用时返回。我们来比较一下IL。

.method public hidebysig instance int32 M() cil managed 
{ 
    // Code size  20 (0x14) 
    .maxstack 2 
    .locals ([0] int32 V_0) 
    IL_0000: ldsfld  int32 '[email protected][email protected]@[email protected]@[email protected]' 
    IL_0005: ldc.i4.1 
    IL_0006: add 
    IL_0007: stsfld  int32 '[email protected][email protected]@[email protected]@[email protected]' 
    IL_000c: ldsfld  int32 '[email protected][email protected]@[email protected]@[email protected]' 
    IL_0011: stloc.0 
    IL_0012: ldloc.0 
    IL_0013: ret 
} // end of method Class1::M 

.method public hidebysig instance int32 M2() cil managed 
{ 
    // Code size  20 (0x14) 
    .maxstack 2 
    .locals ([0] int32 V_0) 
    IL_0000: ldsfld  int32 CppClassLibrary.Class1::i 
    IL_0005: ldc.i4.1 
    IL_0006: add 
    IL_0007: stsfld  int32 CppClassLibrary.Class1::i 
    IL_000c: ldsfld  int32 CppClassLibrary.Class1::i 
    IL_0011: stloc.0 
    IL_0012: ldloc.0 
    IL_0013: ret 
} // end of method Class1::M2 

相同。唯一的区别是字段名称。它使用在CIL中合法的字符,但在C++中是非法的,因此在C++代码中不能使用相同的名称。 C#编译器经常将这个技巧用于自动生成的字段。唯一的区别是静态变量不能通过反射访问 - 我不知道它是如何完成的。

让我们来看一个更有趣的例子。

int M3(int a) { 
    static int i = a; 
    i++; 
    return i; 
} 

现在有趣的开始。静态变量不能在编译时被初始化。它必须在运行时完成。编译器必须确保它只被初始化一次,所以它必须是线程安全的。

产生的CIL是

.method public hidebysig instance int32 M3(int32 a) cil managed 
{ 
    // Code size  73 (0x49) 
    .maxstack 2 
    .locals ([0] int32 V_0) 
    IL_0000: ldsflda int32 '[email protected][email protected]@[email protected]@[email protected]@4HA'            
    IL_0005: call  void _Init_thread_header_m(int32 modreq([mscorlib]System.Runtime.CompilerServices.IsVolatile)*) 
    IL_000a: ldsfld  int32 '[email protected][email protected]@[email protected]@[email protected]@4HA' 
    IL_000f: ldc.i4.m1 
    IL_0010: bne.un.s IL_0035 
    .try 
    { 
    IL_0012: ldarg.1 
    IL_0013: stsfld  int32 '[email protected][email protected]@[email protected]@[email protected]@4HA' 
    IL_0018: leave.s IL_002b 
    } // end .try 
    fault 
    { 
    IL_001a: ldftn  void _Init_thread_abort_m(int32 modreq([mscorlib]System.Runtime.CompilerServices.IsVolatile)*) 
    IL_0020: ldsflda int32 '[email protected][email protected]@[email protected]@[email protected]@4HA' 
    IL_0025: call  void ___CxxCallUnwindDtor(method void *(void*), 
                void*) 
    IL_002a: endfinally 
    } // end handler 
    IL_002b: ldsflda int32 '[email protected][email protected]@[email protected]@[email protected]@4HA' 
    IL_0030: call  void _Init_thread_footer_m(int32 modreq([mscorlib]System.Runtime.CompilerServices.IsVolatile)*) 
    IL_0035: ldsfld  int32 '[email protected][email protected]@[email protected]@[email protected]@4HA' 
    IL_003a: ldc.i4.1 
    IL_003b: add 
    IL_003c: stsfld  int32 '[email protected][email protected]@[email protected]@[email protected]@4HA' 
    IL_0041: ldsfld  int32 '[email protected][email protected]@[email protected]@[email protected]@4HA' 
    IL_0046: stloc.0 
    IL_0047: ldloc.0 
    IL_0048: ret 
} // end of method Class1::M3 

看起来要复杂得多。第二个静态字段,看起来像一个关键部分(尽管我找不到有关_Init_thread_*方法的任何信息)。

它看起来不再那么琐碎。性能也受到影响。恕我直言,这是一个很好的决定,不在C#中实现静态变量。

总之,

为了支持静态变量的C#编译器必须:

  1. 为变量创建一个私有静态字段,确保名称是唯一的,不能直接使用在C#代码中。
  2. 通过反射使该字段不可见。
  3. 如果在编译时无法完成初始化,请使其处于线程安全状态。

看起来好像不多,但是如果将几个这样的功能结合起来,复杂性就会呈指数级增长。

而你唯一得到的回报是一个简单的,编译器提供的,线程安全的初始化。

仅仅因为其他语言支持它而将某项功能添加到语言中并不是一个好主意。在真正需要时添加该功能。 C#设计团队已经犯了这个错误array covariance

+0

*唯一的区别是静态变量不能通过反射访问*如果我要执行这段C#代码会发生什么:'Array.ConvertAll(typeof(Class1).GetFields(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags。公开),FI => fi.GetValue(空))'? GetFields是否不会为该静态字段或GetValue抛出异常返回FieldInfo对象? – PetSerAl

+0

@PetSerAl'GetFields'只返回一个元素,'private static int i'。它不会为静态变量返回一个FieldInfo。 –

+0

非常感谢你,看到你聪明的做法让人耳目一新。也感谢你解释CIL,因为我会解决它的问题:-)。 – greenoldman

1

我的想法是,你需要开始在初始化程序上放置'隐形'锁。

考虑两个线程同时上课的情况Foo.UseStatic;

class Foo 
{ 
    static int counter = 0; 

    void UsesStatic() 
    { 
     static int bar = (counter++) + (counter++); 
    } 
} 

bar基于counter++初始化可能是一个线程的噩梦。 (请看interlocked。)

如果同时有10个线程调用这个代码,那么bar可能会以任何旧值结束。一把锁可以使事情稳定下来,但是,如果没有用户的说法,你就插入了这个大胆的性能障碍。

编辑:添加新的场景。

@greenoldman的评论表明,这个简单的例子可以处理。但是C#充满了语法糖,它转化为不同的“基本”结构。例如,闭包被转换为带有字段的类,using语句变为try/finally块,等待的调用成为传递的回调,并且迭代器方法成为状态机。

编译器在静态变量初始化发生时是否必须处理任何特殊情况?我们确信这会起作用吗?我的猜测是,C#团队看着那个,并且认为'这是一个纯粹的bug来源',并且很好的独立。

+1

非常好和简洁的方式来解释静态变量的问题。如果答案在上面,许多滚动轮的寿命将会增加。 –

+0

谢谢,但这里对静态变量本身没有任何问题。在你的情况下,静态变量不是用本地数据初始化的,所以它可以安全地移动为静态字段,并且正好面对你所描述的同样的问题。换句话说,你描述的问题不是因为静态变量**而是因为你使用的“扭曲”表达式。 – greenoldman

+0

我在考虑静态构造函数保证只运行一次,因此在静态构造函数中分配'bar'只是一个普通的“赋值字段”语句,并且很容易推理,因为它只被调用一次。在一个静态构造函数中,当'bar'被赋值时,没有什么可以改变'counter',所以它是可以预测的。我还附加了更多的“编译器噩梦”场景,展示了如何试图证明许多其他语言功能的交互可能是疯狂的。不是一个完整的论点,而是要讨论的问题 –