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

C# Returning a pointer created with stackalloc inside a function

我有与 C++ 代码交互的 C# 代码,后者对字符串执行操作。

我在静态助手中有这段代码class:

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;
    }
}

如您所见,它 returns 指向使用 stackalloc 关键字创建的字节的指针。
但是从 C# 规范 18.8:

All stack allocated memory blocks created during the execution of a function member are automatically discarded when that function member returns.

是不是说这个指针实际上是在方法returns后就失效了?

方法的当前用法:

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()EncodingGetBytes() 的其他重载中的 fixed 语句).

tl;博士

  1. 方法returns指针一失效?在传给DirectFunction()的时候是不是无效?

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

stackalloc 导致在堆栈上分配内存。当函数 returns 时堆栈自动展开。 C# 通过不让您 return 指针来保护您免于创建挂起指针,因为当函数 returns.[= 展开堆栈后,内存不可能仍然有效。 10=]

如果您希望内存超出分配它的函数的范围,则不能在堆栈上分配它。你必须通过 new 在堆上分配。

Does it mean that the pointer is actually invalid as soon as the method returns?

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

这个场景具体是提议的未来ref改变希望目标的场景之一,意思是:允许stackalloc进入ref(而不是指针),与编译器知道它是堆栈引用 ref 或类引用类型,因此不允许 ref-return 该值。

最终,当您输入 unsafe 时,您输入的是 "I take full responsibility if this goes wrong"。在这种情况下,确实是错误的。


在离开方法之前使用指针是有效的,所以一个可行的方法可能是(假设你想要一个相当通用的目的 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);
    }
}

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