1

我有与 C++ 代码交互的 C# 代码,它使用字符串执行操作。

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

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# Specifications 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(包括fixed其他重载 ofGetByteCount()GetBytes()of中的语句Encoding)。

tl;博士

  1. 方法返回后指针是否无效?它在传递给时是否无效DirectFunction()

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

4

2 回答 2

2

这是否意味着一旦方法返回,指针实际上就无效了?

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

这种情况特别是提议的未来ref更改希望针对的情况之一,这意味着:允许stackalloc进入ref(而不是指针),编译器知道它是堆栈引用ref或类似引用的类型,因此不允许ref-return那个值。

最终,当您键入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);
    }
}

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

于 2017-05-06T08:28:27.673 回答
1

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

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

于 2017-05-06T07:23:38.857 回答