C# Возвращает указатель, созданный с помощью stackalloc внутри функции
У меня есть код 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# 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
заявления в других перегрузках GetByteCount()
а также GetBytes()
из Encoding
).
ТЛ; др
Указатель недействителен, как только метод возвращается? Является ли это недействительным в момент передачи
DirectFunction()
?Если так, то как лучше всего использовать наименьшее
fixed
заявления для достижения цели?
2 ответа
Означает ли это, что указатель действительно неверен, как только метод вернется?
Да, он технически недействителен, хотя почти наверняка не будет обнаружен. Этот сценарий сам по себе через unsafe
, Любое действие в этой памяти теперь имеет неопределенное поведение. Все, что вы делаете, но в частности вызываете методы, может случайно перезаписать эту память - или нет - в зависимости от относительных размеров стека и глубины стека.
Этот сценарий является одним из тех, которые предложены в будущем ref
меняет надежду на цель, что означает: stackalloc
в ref
(а не указатель), при этом компилятор знает, что он ссылается на стек ref
или 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);
}
}
Также обратите внимание, что очень большие строки могут привести к переполнению стека.
stackalloc вызывает выделение памяти в стеке. Стек автоматически разматывается, когда функция возвращается. C# защищает вас от создания зависшего указателя, не позволяя вам вернуть указатель, так как нет никакого способа, которым память все еще могла бы быть действительной после того, как стек разматывается, когда функция возвращается.
Если вы хотите, чтобы память находилась за пределами функции, выделяющей ее, вы не можете выделить ее в стеке. Вы должны выделить в кучу через новый.