C#中的不安全代码是否会导致内存损坏?

9
基本上,内存损坏是由于覆盖了不应该覆盖的内存所引起的。我想知道在C#的不安全代码中是否可能发生这种情况(即不通过调用外部非托管代码)。我看到两种可能的情况:
  • 访问空指针 -> 被CLR捕获,抛出NullReferenceException
  • 访问指向无效随机内存位置的指针 -> 被CLR捕获,抛出AccessViolationException
在这两种情况下,似乎运行时检测并防止潜在的内存损坏发生。因此,是否可能使用不安全代码从C#破坏内存?作为推论,从不安全代码中捕获AccessViolationExceptions是否安全?

在C#中,它是一个NullReferenceException。我怀疑clr会为托管和非托管的空引用抛出相同的异常。 - user180326
已修复,谢谢。是同一个异常,你可以轻松地自己尝试一下,例如 var ptr = (int*)0; *ptr = 1; // throws NullReferenceException - Asik
请注意,在 .net 4.0 中,如果存在损坏状态的迹象,默认情况下将终止进程。http://msdn.microsoft.com/en-us/magazine/dd419661.aspx#Four - user180326
感谢测试...今天学到了一些东西。 - user180326
1
你缺少第三种情况:访问指向有效内存位置的指针,其中包含不能由您修改的数据。不被CLR捕获,没有异常,会破坏内存。 - Eric Lippert
1
更普遍地说,“不安全代码”功能被称为“不安全”,因为它是不安全的。CLR不能保证能够从你的错误中拯救你;这就是为什么它被称为“不安全”的原因。 - Eric Lippert
4个回答

12

你缺少了一个很重要的点:

  • 访问内存但没有收到AccessViolationException异常。

这种情况非常常见,在.NET程序中总是有大量可写的内存。包括写入数组末尾以外的元素,它很少会失败。内存保护在内存页边界上是分段的,在Windows上是4096字节。 .NET GC将其显著提高,分代堆段是非常大的VM块。捕获AVE是非常不明智的。

以下是一些代码可供使用:

class Program {
    static unsafe void Main(string[] args) {
        var arr = new byte[1];
        for (int fill = 0; fill < 2 * 1024 - 64; ++fill) {
            byte[] dummy = new byte[1024];
        }
        fixed (byte* p = &arr[0]) {
            for (int ix = 1; ; ++ix)
                p[ix] = 42;
        }
    }
}

超出了约1.5兆字节。


10
访问指向无效的随机内存位置
如果是读取,则安全。如果是写入,并且内存位置刚好有效但不属于你,则不安全。你会覆盖随机内存位置。
作为必然结果,从不安全的代码中捕获AccessViolationExceptions是否安全?
不,因为该异常告诉你a)存在错误,b)可能已损坏内存,无法修复。立即捕获并关闭进程。

如果是写入操作且内存位置恰好有效但你没有拥有它,那么会是不安全的。如果超出了应用程序的边界,据我所知,Windows 将会终止你的进程。 - Geeky Guy
1
我不太理解为什么会在抛出 AccessViolationException 异常时导致内存损坏。难道它被抛出的事实不意味着 CLR 已经拦截了访问无效内存的尝试,因此没有允许它发生吗? - Asik
1
异常可能在发生了静默损坏后被抛出。此外,如果您跨越页面边界编写,则可能会产生一半写入的情况。 - usr
2
即使您拥有内存位置,编写也可能会破坏事物。不安全的代码可能会在托管对象、堆数据结构、堆栈帧等上方涂鸦。 - Jim Mischel
哦...所以这取决于我们所说的“拥有”的含义。不过,好吧。我明白你的观点了。 - Jim Mischel
显示剩余2条评论

7
不安全的C#代码会导致内存损坏吗?答案是肯定的!考虑以下示例:
int a = 10;
int* p = &a;
*(p+54)= 444;

CLR可能会捕获这个错误,也可能不会。

并非所有通过坏指针进行的读取或写入都会导致访问冲突,因此访问冲突通常表示已经通过坏指针进行了多次读取或写入,并且可能会破坏内存。

来源:Accessviolationexception文档


2

引起AccessViolationException的内存写操作并未发生,因此它并没有做任何事情。

然而:它试图写入错误的位置是一个非常强烈的指示,表明之前可能存在无效的写入操作(因为您的代码有缺陷),而这些写入可能指向可写但不应更改的位置。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接