C#返回在函数内部用stackalloc创建的指针

问题描述:

我有C#代码与C++代码交互,C++代码使用字符串执行操作。C#返回在函数内部用stackalloc创建的指针

我有一个静态辅助类这段代码:

internal static unsafe byte* GetConstNullTerminated(string text, Encoding encoding) 
{ 
    int charCount = text.Length; 
    fixed (char* chars = text) 
    { 
     int byteCount = encoding.GetByteCount(chars, charCount); 
     byte* bytes = stackalloc byte[byteCount + 1]; 
     encoding.GetBytes(chars, charCount, bytes, byteCount); 
     *(bytes + byteCount) = 0; 
     return bytes; 
    } 
} 

正如你可以看到,它返回一个指向与stackalloc关键字创建的字节数。
然而从C#规格18.8:

函数成员的执行期间创建的所有堆栈分配的存储块时功能部件返回自动丢弃。

这是否表示该方法返回时指针实际上是无效的?

电流的方法的用法:

byte* bytes = StringHelper.GetConstNullTerminated(value ?? string.Empty, Encoding); 
DirectFunction(NativeMethods.SCI_SETTEXT, UIntPtr.Zero, (IntPtr) bytes); 

应的代码改为

... 
int byteCount = encoding.GetByteCount(chars, charCount); 
byte[] byteArray = new byte[byteCount + 1]; 
fixed (byte* bytes = byteArray) 
{ 
    encoding.GetBytes(chars, charCount, bytes, byteCount); 
    *(bytes + byteCount) = 0; 
} 
return byteArray; 

和阵列的再次使用fixed返回,将指针传递给DirectFunction方法?

我试图尽量减少fixed用途(包括在GetByteCount()其他重载的fixed报表和EncodingGetBytes())的数量。

TL;博士

  1. 是指针无效的,因为一旦方法返回?在传递给DirectFunction()时它是无效的吗?

  2. 如果是这样,使用最少的fixed语句来完成任务的最佳方式是什么?

这是否表示该方法返回时指针实际上是无效的?

是的,它在技术上是无效的 - 虽然它几乎肯定不会被检测到。这种情况是通过unsafe自己造成的。对该内存的任何操作现在都有未定义的行为。您所做的任何事情,特别是调用方法,都可能会随机覆盖该内存 - 或不是 - 取决于相对的堆栈帧大小和深度。

此方案具体为今后拟ref改变希望的目标,这意味着那些之一:允许stackallocref(而不是一个指针),与编译器知道这是一个堆栈 - 参考ref或参考 - 类型,因此不允许ref - 返回该值。

最终,当你输入unsafe时,你会说“如果出现这种情况,我会全权负责”。在这种情况下,这确实是错误的。


有效使用指针之前离开的方法,因此一个可行的办法可能是(假设哟想一个相当通用的API),允许呼叫者委托或接口传递指定来电要你做指针的东西,即

StringHelper.GetConstNullTerminated(value ?? string.Empty, Encoding, 
    ptr => DirectFunction(NativeMethods.SCI_SETTEXT, UIntPtr.Zero, (IntPtr) ptr)); 

有:

unsafe delegate void PointerAction(byte* ptr); 
internal static unsafe void GetConstNullTerminated(string text, Encoding encoding, 
    PointerAction action) 
{ 
    int charCount = text.Length; 
    fixed (char* chars = text) 
    { 
     int byteCount = encoding.GetByteCount(chars, charCount); 
     byte* bytes = stackalloc byte[byteCount + 1]; 
     encoding.GetBytes(chars, charCount, bytes, byteCount); 
     *(bytes + byteCount) = 0; 
     action(bytes); 
    } 
} 

还要注意,非常大的字符串可能会导致堆栈溢出。

+0

从来没有想过要通过一个代表。如果不存在使用委托的性能下降,该解决方案将像手动将'GetConstNullTerminated'内嵌到目标中一样快。我现在必须做一些关于代表性能的研究... –

+0

@AARon代表的性能将与编码工作和输入字符串的双重行程相比完全不可估量(一次为长度,一次为编码) –

+0

代表实际上只是在堆上分配而不是提高性能?代表仍然需要堆分配。根据传递的字符串的大小,您可以轻松地分配更多的内存来容纳委托,而不是实际的字符串。 –

stackalloc导致内存分配到堆栈上。函数返回时堆栈会自动展开。 C#通过不让你返回指针来保护你不会创建一个悬挂指针,因为当函数返回时解开堆栈后,内存仍然没有可能有效。

如果您希望内存超出分配函数的范围,您无法将其分配到堆栈上。你必须通过新分配堆。

+0

“C#通过不让你返回指针来保护你不会创建悬挂指针”C#根本不抱怨,代码编译没有问题。 –

+0

我的歉意。我认为C#有一些基本的逃逸分析来捕捉这种情况。 –