我正在阅读ECMA-334,正如一位为生活而编程的朋友所建议的。我在处理不安全代码的部分。虽然,我对他们所谈论的内容感到有点困惑。
C#底层的垃圾收集器可能通过移动对象来工作 在内存中,但这个动作对于大多数C#开发人员来说是不可见的。 对于通常满足自动内存的开发人员 管理,但有时需要细粒度的控制或额外的一点 在性能方面,C#提供了编写“不安全”代码的能力。这样 代码可以直接处理指针类型和对象地址; 但是,C#要求程序员临时修复对象 防止垃圾收集器移动它们。这个“不安全”的代码 从两者的角度来看,功能实际上都是一个“安全”功能 开发人员和用户。不安全的代码应在代码中清楚标明 使用修饰符不安全,因此开发人员不可能使用不安全 意外的语言功能,以及编译器和执行 发动机一起工作,确保26 8 9Banguage概述不安全 代码不能伪装成安全代码。这些限制限制了使用 不安全的代码到代码可信的情况。
示例
using System;
class Test
{
static void WriteLocations(byte[] arr)
{
unsafe
{
fixed (byte* pArray = arr)
{
byte* pElem = pArray;
for (int i = 0; i < arr.Length; i++)
{
byte value = *pElem;
Console.WriteLine("arr[{0}] at 0x{1:X} is {2}",
i, (uint)pElem, value);
pElem++;
}
}
}
}
static void Main()
{
byte[] arr = new byte[] { 1, 2, 3, 4, 5 };
WriteLocations(arr);
Console.ReadLine();
}
}
显示了一个名为WriteLocations的方法中的一个不安全的块,它修复了一个 数组实例并使用指针操作迭代 元素。每个数组元素的索引,值和位置 写到控制台。一个可能的输出示例是:
arr[0] at 0x8E0360 is 1 arr[1] at 0x8E0361 is 2 arr[2] at 0x8E0362 is 3 arr[3] at 0x8E0363 is 4 arr[4] at 0x8E0364 is 5
但是,当然,确切的内存位置可能会有所不同 不同的应用程序执行。
为什么知道确切的内存位置,例如,这个数组对开发人员有益吗?有人可以在简化的背景下解释这个理想吗?
答案 0 :(得分:5)
fixed
语言功能并不完全是&#34;有益的&#34;因为它是绝对必要的&#34;。
通常,C#用户会将Reference-types视为等同于单个间接指针(例如,对于class Foo
,这个:Foo foo = new Foo();
等同于此C ++:Foo* foo = new Foo();
。
实际上,C#中的引用更接近双间接指针,它是指向大型对象表中的条目的指针(或者更确切地说是句柄),然后存储对象的实际地址。 GC不仅会清理未使用的对象,还会清除also move objects around in memory以避免内存碎片。
如果您在C#中专门使用对象引用,那么这一切都很好。一旦你使用指针,你就会遇到问题,因为GC可以及时在任何点运行,即使在紧密循环执行期间,以及当GC运行你的程序时执行被冻结(这就是CLR和Java不适合硬实时应用程序的原因 - 在某些情况下,GC暂停可以持续几百毫秒)。
...由于这种固有行为(在代码执行期间移动对象),您需要阻止该对象被移动,因此fixed
关键字指示GC不要移动该对象。< / p>
一个例子:
unsafe void Foo() {
Byte[] safeArray = new Byte[ 50 ];
safeArray[0] = 255;
Byte* p = &safeArray[0];
Console.WriteLine( "Array address: {0}", &safeArray );
Console.WriteLine( "Pointer target: {0}", p );
// These will both print "0x12340000".
while( executeTightLoop() ) {
Console.WriteLine( *p );
// valid pointer dereferencing, will output "255".
}
// Pretend at this point that GC ran right here during execution. The safeArray object has been moved elsewhere in memory.
Console.WriteLine( "Array address: {0}", &safeArray );
Console.WriteLine( "Pointer target: {0}", p );
// These two printed values will differ, demonstrating that p is invalid now.
Console.WriteLine( *p )
// the above code now prints garbage (if the memory has been reused by another allocation) or causes the program to crash (if it's in a memory page that has been released, an Access Violation)
}
因此,通过将fixed
应用于safeArray
对象,指针p
将始终是有效指针,不会导致崩溃或处理垃圾数据。
附注:fixed
的替代方法是使用stackalloc
,但这会将对象生命周期限制在函数范围内。
答案 1 :(得分:2)
通常,“不安全”块中的确切内存位置不太相关。
如Dai`s answer中所述,当您使用垃圾收集器管理的内存时,您需要确保您正在操作的数据不会被移动(使用“fixed”)。你通常在
时使用它在某些情况下,您正在使用不受垃圾收集器管理的内存,此类方案的一些示例如下:
答案 2 :(得分:1)
我使用fixed
的主要原因之一是与本机代码进行交互。假设您有一个带有以下签名的本机函数:
double cblas_ddot(int n, double* x, int incx, double* y, int incy);
你可以像这样编写一个interop包装器:
public static extern double cblas_ddot(int n, [In] double[] x, int incx,
[In] double[] y, int incy);
编写C#代码来调用它:
double[] x = ...
double[] y = ...
cblas_dot(n, x, 1, y, 1);
但是现在假设我想对我的数组中间的一些数据进行操作,从x [2]和y [2]开始。没有复制数组就无法进行调用。
double[] x = ...
double[] y = ...
cblas_dot(n, x[2], 1, y[2], 1);
^^^^
this wouldn't compile
在这种情况下,固定来救援。我们可以更改互操作的签名并使用来自调用者的固定。
public unsafe static extern double cblas_ddot(int n, [In] double* x, int incx,
[In] double* y, int incy);
double[] x = ...
double[] y = ...
fixed (double* pX = x, pY = y)
{
cblas_dot(n, pX + 2, 1, pY + 2, 1);
}
我也在极少数情况下使用了固定的,我需要快速循环数组,并且需要确保.NET数组边界检查没有发生。