使不安全的代码在C#安全
我最近通过文章image processing in C#使不安全的代码在C#安全
阅读有一个在那里,我真的不喜欢,因为它是不安全的,我想一些代码知道它是否能进行安全:
public static bool Invert(Bitmap b)
{
// GDI+ still lies to us - the return format is BGR, NOT RGB.
BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height),
ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
int stride = bmData.Stride;
System.IntPtr Scan0 = bmData.Scan0;
unsafe
{
byte * p = (byte *)(void *)Scan0;
int nOffset = stride - b.Width*3;
int nWidth = b.Width * 3;
for(int y=0;y < b.Height;++y)
{
for(int x=0; x < nWidth; ++x)
{
p[0] = (byte)(255-p[0]);
++p;
}
p += nOffset;
}
}
b.UnlockBits(bmData);
return true;
}
行byte* p = (byte*)(void*)Scan0;
模样的罪魁祸首,但我不得不说,我真的不明白它在做什么,或者它如何可以作出安全。
任何人都可以对此有所了解吗?
不安全的代码主要用于性能方面的原因。基本思想是你要逐字节地遍历图像数据,并手动翻转每个字节(虽然有更高效和简单的方法来处理相同的事情)。
底层图像由GDI +处理,这是非托管代码。所以当你直接使用图像字节时,你必须操纵非托管内存。这是多么安全或不安全确实是令人惊讶的棘手 - 它取决于非托管内存最初如何分配。假设您使用托管代码工作,并且您可能从文件或某个流中加载了位图,实际上它可能并不真正不安全 - 例如,您无法意外覆盖托管内存。 unsafe
关键字的名称并不是来自固有的危险 - 它来自允许你做非常不安全的事情。例如,如果您在自己的托管堆栈上为位图分配了内存,则可能会让事情变得糟糕。
总的来说,如果你真的能证明它是值得的成本,那么只使用不安全的代码是一个好习惯。在图像处理中,这通常是一个很好的折衷 - 您正在处理大量简单的数据,其中的开销例如边界检查可能很重要,即使只能验证一次,而不是在每个循环的迭代中都很容易。
如果你想摆脱这种不安全的代码,一个办法是分配自己byte[]
(管理),使用Marshal.Copy
将图像数据复制到这个byte[]
,做管理的阵列中所做的修改,然后再复制结果再次使用Marshal.Copy
。问题是,这意味着分配一个与原始图像一样大的byte[]
,然后将其复制两次(在这种情况下边界检查可以忽略不计 - .NET JIT编译器会优化它)。最后,它仍然是可能的在使用Marshal.Copy
时会出现错误,这会给您带来与unsafe
相同的问题(不完全是这样,但这将会花费更长的时间)。
对我而言,unsafe
作为关键字的最有价值的部分是它可以让你本地化你正在做的不安全的东西。虽然典型的非托管应用程序是不安全的,但C#只允许您在代码的特定标记部分中不安全。虽然这些仍然会影响其他代码(这是在FullTrust环境中只能使用unsafe
的原因之一),但它使它们更易于调试和控制。这是一个权衡,一如既往。
但是,代码实际上是以非常不同的方式不安全的 - 如果在代码的中间存在异常,则可能永远不会发生调用UnlockBits
。您应该使用finally
子句确保properrer清理非托管资源。
最后,如果你想要“真正”的性能,安全或不安全,你可能不会在CPU上进行图像处理。今天,假设您正在运行的计算机具有GPU,可以更快,更轻松地完成工作,并且完全与计算机本身运行的代码完全隔离,这往往是安全的。
如果你不知道它在做什么,为什么你想“让它安全”?你害怕“不安全”这个词吗?该代码用于使用指针直接访问内存中的位图像素。另一种方法是使用GetPixel()来编组每个像素的数据,速度非常慢。 – CodeCaster
'Scan0'是一个'IntPtr',所以如果你想获得相同的处理速度,你基本上必须使用指针。 –
“我怎样才能让它不安全?” - 停止使用指针。如果你不知道_why_它的使用标记,那么就不要惹它(或找到某人知道它在做什么和_why_)。 –