静态变量和静态字段的实现区别是什么?
What is the implementation difference between static variable and static field?
此题是从编译器实现的角度。
我想知道 C# 中的静态变量,我找到了它们未实现的原因的解释(此处:http://blogs.msdn.com/b/csharpfaq/archive/2004/05/11/why-doesn-t-c-support-static-method-variables.aspx)。
引用 "it is possible to get nearly the same effect by having a class-level static" -- 这让我很好奇,有什么区别?假设 C# 将具有静态变量语法——实现可能是 "push this silently as static field and leave conditional initialization (if necessary)"。完成。
我唯一能发现的是给定初始化值类型的问题。还有其他适合 "nearly" 的东西吗?
我重新表述问题——如何在 C# 编译器中使用 仅 现有功能实现静态变量(因此静态变量必须以当前状态术语在内部创建)。
实际上很容易检查编译器必须做什么才能在 C# 中实现静态变量。
C# 旨在编译为 CIL(通用中间语言)。支持静态变量的C++也可以编译成CIL。
让我们看看当我们这样做时会发生什么。首先,让我们考虑以下简单的 class:
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,每次调用方法时递增和 returned。让我们比较一下IL。
.method public hidebysig instance int32 M() cil managed
{
// Code size 20 (0x14)
.maxstack 2
.locals ([0] int32 V_0)
IL_0000: ldsfld int32 '?i@?1??M@Class1@CppClassLibrary@@Q$AAMHXZ@4HA'
IL_0005: ldc.i4.1
IL_0006: add
IL_0007: stsfld int32 '?i@?1??M@Class1@CppClassLibrary@@Q$AAMHXZ@4HA'
IL_000c: ldsfld int32 '?i@?1??M@Class1@CppClassLibrary@@Q$AAMHXZ@4HA'
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 '?$TSS0@?1??M3@Class1@CppClassLibrary@@Q$AAMHH@Z@4HA'
IL_0005: call void _Init_thread_header_m(int32 modreq([mscorlib]System.Runtime.CompilerServices.IsVolatile)*)
IL_000a: ldsfld int32 '?$TSS0@?1??M3@Class1@CppClassLibrary@@Q$AAMHH@Z@4HA'
IL_000f: ldc.i4.m1
IL_0010: bne.un.s IL_0035
.try
{
IL_0012: ldarg.1
IL_0013: stsfld int32 '?i@?1??M3@Class1@CppClassLibrary@@Q$AAMHH@Z@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 '?$TSS0@?1??M3@Class1@CppClassLibrary@@Q$AAMHH@Z@4HA'
IL_0025: call void ___CxxCallUnwindDtor(method void *(void*),
void*)
IL_002a: endfinally
} // end handler
IL_002b: ldsflda int32 '?$TSS0@?1??M3@Class1@CppClassLibrary@@Q$AAMHH@Z@4HA'
IL_0030: call void _Init_thread_footer_m(int32 modreq([mscorlib]System.Runtime.CompilerServices.IsVolatile)*)
IL_0035: ldsfld int32 '?i@?1??M3@Class1@CppClassLibrary@@Q$AAMHH@Z@4HA'
IL_003a: ldc.i4.1
IL_003b: add
IL_003c: stsfld int32 '?i@?1??M3@Class1@CppClassLibrary@@Q$AAMHH@Z@4HA'
IL_0041: ldsfld int32 '?i@?1??M3@Class1@CppClassLibrary@@Q$AAMHH@Z@4HA'
IL_0046: stloc.0
IL_0047: ldloc.0
IL_0048: ret
} // end of method Class1::M3
看起来复杂多了。第二个静态字段,看起来像一个关键部分(虽然我找不到任何关于 _Init_thread_*
方法的信息)。
看起来不再那么琐碎了。性能也会受到影响。恕我直言,不在 C# 中实现静态变量是一个很好的决定。
总而言之,
要支持静态变量,C# 编译器必须:
- 为变量创建一个私有静态字段,确保名称是唯一的,不能直接在 C# 代码中使用。
- 通过反射使该字段不可见。
- 如果无法在编译时进行初始化,请使其成为线程安全的。
看起来不多,但如果像这样组合几个特征,复杂度会呈指数级上升。
您在 return 中获得的唯一东西是一个简单的、编译器提供的、线程安全的初始化。
仅因为其他语言支持而向语言添加功能不是一个好主意。在真正需要时添加功能。 C# 设计团队已经用 array covariance
犯了这个错误
我的想法是您需要开始 'invisible' 锁定初始化程序。
考虑两个线程同时存在的情况class Foo.UseStatic;
class Foo
{
static int counter = 0;
void UsesStatic()
{
static int bar = (counter++) + (counter++);
}
}
基于counter++
的bar
初始化可能是线程噩梦。 (为此请查看 interlocked class。)
如果十个并发线程调用此代码,bar 可能以任何旧值结束。锁会使事情稳定下来,但是你在没有用户同意的情况下插入了这个大而生硬的性能障碍。
编辑:添加了新场景。
@greenoldman 的评论表明可以处理这个简单的示例。但是 C# 充满了语法糖,可以转换为不同的 'basic' 结构。例如,闭包被转换为带有字段的 classes,using
语句变成 try/finally 块,等待的调用变成传递的回调,迭代器方法变成状态机。
那么当静态变量初始化发生时,编译器是否必须处理任何特殊情况?我们有信心这会奏效吗?
async Task<int> UsesStatic(int defaultValue)
{
static int bar;
try
{
throw new Exception("Boom!");
}
catch
{
using(var errorLogger = Log.NewLogger("init failed")
{
// here's the awaited call;
bar = await service.LongRunningCall(() => Math.Abs(defaultValue));
// that'll fail;
throw new Exception("Oh FFS!");
}
}
finally
{
bar = 0;
}
return bar;
}
我的猜测是,C# 团队查看了它,并认为“那是一个纯粹的错误来源”,然后就没有管它了。
此题是从编译器实现的角度。
我想知道 C# 中的静态变量,我找到了它们未实现的原因的解释(此处:http://blogs.msdn.com/b/csharpfaq/archive/2004/05/11/why-doesn-t-c-support-static-method-variables.aspx)。
引用 "it is possible to get nearly the same effect by having a class-level static" -- 这让我很好奇,有什么区别?假设 C# 将具有静态变量语法——实现可能是 "push this silently as static field and leave conditional initialization (if necessary)"。完成。
我唯一能发现的是给定初始化值类型的问题。还有其他适合 "nearly" 的东西吗?
我重新表述问题——如何在 C# 编译器中使用 仅 现有功能实现静态变量(因此静态变量必须以当前状态术语在内部创建)。
实际上很容易检查编译器必须做什么才能在 C# 中实现静态变量。
C# 旨在编译为 CIL(通用中间语言)。支持静态变量的C++也可以编译成CIL。
让我们看看当我们这样做时会发生什么。首先,让我们考虑以下简单的 class:
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,每次调用方法时递增和 returned。让我们比较一下IL。
.method public hidebysig instance int32 M() cil managed
{
// Code size 20 (0x14)
.maxstack 2
.locals ([0] int32 V_0)
IL_0000: ldsfld int32 '?i@?1??M@Class1@CppClassLibrary@@Q$AAMHXZ@4HA'
IL_0005: ldc.i4.1
IL_0006: add
IL_0007: stsfld int32 '?i@?1??M@Class1@CppClassLibrary@@Q$AAMHXZ@4HA'
IL_000c: ldsfld int32 '?i@?1??M@Class1@CppClassLibrary@@Q$AAMHXZ@4HA'
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 '?$TSS0@?1??M3@Class1@CppClassLibrary@@Q$AAMHH@Z@4HA'
IL_0005: call void _Init_thread_header_m(int32 modreq([mscorlib]System.Runtime.CompilerServices.IsVolatile)*)
IL_000a: ldsfld int32 '?$TSS0@?1??M3@Class1@CppClassLibrary@@Q$AAMHH@Z@4HA'
IL_000f: ldc.i4.m1
IL_0010: bne.un.s IL_0035
.try
{
IL_0012: ldarg.1
IL_0013: stsfld int32 '?i@?1??M3@Class1@CppClassLibrary@@Q$AAMHH@Z@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 '?$TSS0@?1??M3@Class1@CppClassLibrary@@Q$AAMHH@Z@4HA'
IL_0025: call void ___CxxCallUnwindDtor(method void *(void*),
void*)
IL_002a: endfinally
} // end handler
IL_002b: ldsflda int32 '?$TSS0@?1??M3@Class1@CppClassLibrary@@Q$AAMHH@Z@4HA'
IL_0030: call void _Init_thread_footer_m(int32 modreq([mscorlib]System.Runtime.CompilerServices.IsVolatile)*)
IL_0035: ldsfld int32 '?i@?1??M3@Class1@CppClassLibrary@@Q$AAMHH@Z@4HA'
IL_003a: ldc.i4.1
IL_003b: add
IL_003c: stsfld int32 '?i@?1??M3@Class1@CppClassLibrary@@Q$AAMHH@Z@4HA'
IL_0041: ldsfld int32 '?i@?1??M3@Class1@CppClassLibrary@@Q$AAMHH@Z@4HA'
IL_0046: stloc.0
IL_0047: ldloc.0
IL_0048: ret
} // end of method Class1::M3
看起来复杂多了。第二个静态字段,看起来像一个关键部分(虽然我找不到任何关于 _Init_thread_*
方法的信息)。
看起来不再那么琐碎了。性能也会受到影响。恕我直言,不在 C# 中实现静态变量是一个很好的决定。
总而言之,
要支持静态变量,C# 编译器必须:
- 为变量创建一个私有静态字段,确保名称是唯一的,不能直接在 C# 代码中使用。
- 通过反射使该字段不可见。
- 如果无法在编译时进行初始化,请使其成为线程安全的。
看起来不多,但如果像这样组合几个特征,复杂度会呈指数级上升。
您在 return 中获得的唯一东西是一个简单的、编译器提供的、线程安全的初始化。
仅因为其他语言支持而向语言添加功能不是一个好主意。在真正需要时添加功能。 C# 设计团队已经用 array covariance
犯了这个错误我的想法是您需要开始 'invisible' 锁定初始化程序。
考虑两个线程同时存在的情况class Foo.UseStatic;
class Foo
{
static int counter = 0;
void UsesStatic()
{
static int bar = (counter++) + (counter++);
}
}
基于counter++
的bar
初始化可能是线程噩梦。 (为此请查看 interlocked class。)
如果十个并发线程调用此代码,bar 可能以任何旧值结束。锁会使事情稳定下来,但是你在没有用户同意的情况下插入了这个大而生硬的性能障碍。
编辑:添加了新场景。
@greenoldman 的评论表明可以处理这个简单的示例。但是 C# 充满了语法糖,可以转换为不同的 'basic' 结构。例如,闭包被转换为带有字段的 classes,using
语句变成 try/finally 块,等待的调用变成传递的回调,迭代器方法变成状态机。
那么当静态变量初始化发生时,编译器是否必须处理任何特殊情况?我们有信心这会奏效吗?
async Task<int> UsesStatic(int defaultValue)
{
static int bar;
try
{
throw new Exception("Boom!");
}
catch
{
using(var errorLogger = Log.NewLogger("init failed")
{
// here's the awaited call;
bar = await service.LongRunningCall(() => Math.Abs(defaultValue));
// that'll fail;
throw new Exception("Oh FFS!");
}
}
finally
{
bar = 0;
}
return bar;
}
我的猜测是,C# 团队查看了它,并认为“那是一个纯粹的错误来源”,然后就没有管它了。