Mã không an toàn này có nên hoạt động trong .NET Core 3 không?


42

Tôi đang cấu trúc lại các thư viện của mình để sử dụng Span<T>để tránh phân bổ heap nếu có thể nhưng vì tôi cũng nhắm mục tiêu các khung cũ hơn nên tôi cũng đang thực hiện một số giải pháp dự phòng chung. Nhưng bây giờ tôi đã tìm thấy một vấn đề kỳ lạ và tôi không chắc là mình đã tìm thấy một lỗi trong .NET Core 3 hay tôi đang làm gì đó bất hợp pháp.

Vấn đề:

// This returns 1 as expected but cannot be used in older frameworks:
private static uint ReinterpretNew()
{
    Span<byte> bytes = stackalloc byte[4];
    bytes[0] = 1; // FillBytes(bytes);

    // returning bytes as uint:
    return Unsafe.As<byte, uint>(ref bytes.GetPinnableReference());
}

// This returns garbage in .NET Core 3.0 with release build:
private static unsafe uint ReinterpretOld()
{
    byte* bytes = stackalloc byte[4];
    bytes[0] = 1; // FillBytes(bytes);

    // returning bytes as uint:
    return *(uint*)bytes;
}

Thật thú vị, ReinterpretOldhoạt động tốt trong .NET Framework và .NET Core 2.0 (vì vậy tôi có thể hài lòng với nó sau tất cả), tuy nhiên, điều đó làm phiền tôi một chút.

Btw. ReinterpretOldcũng có thể được sửa trong .NET Core 3.0 bằng một sửa đổi nhỏ:

//return *(uint*)bytes;
uint* asUint = (uint*)bytes;
return *asUint;

Câu hỏi của tôi:

Đây có phải là một lỗi hoặc chỉ ReinterpretOldhoạt động trong các khung cũ hơn một cách tình cờ và tôi có nên áp dụng bản sửa lỗi cho chúng không?

Nhận xét:

  • Bản dựng gỡ lỗi cũng hoạt động trong .NET Core 3.0
  • Tôi cố gắng để áp dụng [MethodImpl(MethodImplOptions.NoInlining)]đến ReinterpretOldnhưng nó không có hiệu lực.

2
FYI: return Unsafe.As<byte, uint>(ref bytes[0]);hoặc return MemoryMarshal.Cast<byte, uint>(bytes)[0];- không cần sử dụng GetPinnableReference(); Tuy nhiên, nhìn vào một chút khác
Marc Gravell

SharpLab trong trường hợp nó giúp bất cứ ai khác. Hai phiên bản tránh Span<T>biên dịch sang IL khác nhau. Tôi không nghĩ bạn đang làm bất cứ điều gì không hợp lệ: Tôi nghi ngờ lỗi JIT.
cant7

Rác mà bạn đang nhìn thấy là gì? bạn đang sử dụng hack để vô hiệu hóa local-init? vụ hack này tác động đáng kểstackalloc (tức là nó không xóa sạch không gian được phân bổ)
Marc Gravell

@ canton7 nếu chúng biên dịch thành cùng một IL, chúng ta không thể suy ra đó là lỗi JIT ... nếu IL giống nhau, v.v ... nghe có vẻ giống một lỗi trình biên dịch, nếu có, có lẽ với trình biên dịch cũ hơn? Gyorgy: bạn có thể chỉ ra chính xác cách bạn biên dịch cái này không? SDK gì chẳng hạn? Tôi không thể xử lý rác
Marc Gravell

1
Có vẻ như stackalloc không phải lúc nào cũng bằng không, trên thực tế: link
canton7

Câu trả lời:


35

Ồ, đây là một tìm kiếm thú vị; Điều đang xảy ra ở đây là địa phương của bạn đang được tối ưu hóa - không còn người dân địa phương nào, điều đó có nghĩa là không có .locals init, điều đó có nghĩa là stackallochành vi khác nhau và không xóa sạch không gian;

private static unsafe uint Reinterpret1()
{
    byte* bytes = stackalloc byte[4];
    bytes[0] = 1;

    return *(uint*)bytes;
}

private static unsafe uint Reinterpret2()
{
    byte* bytes = stackalloc byte[4];
    bytes[0] = 1;

    uint* asUint = (uint*)bytes;
    return *asUint;
}

trở thành:

.method private hidebysig static uint32 Reinterpret1() cil managed
{
    .maxstack 8
    L_0000: ldc.i4.4 
    L_0001: conv.u 
    L_0002: localloc 
    L_0004: dup 
    L_0005: ldc.i4.1 
    L_0006: stind.i1 
    L_0007: ldind.u4 
    L_0008: ret 
}

.method private hidebysig static uint32 Reinterpret2() cil managed
{
    .maxstack 3
    .locals init (
        [0] uint32* numPtr)
    L_0000: ldc.i4.4 
    L_0001: conv.u 
    L_0002: localloc 
    L_0004: dup 
    L_0005: ldc.i4.1 
    L_0006: stind.i1 
    L_0007: stloc.0 
    L_0008: ldloc.0 
    L_0009: ldind.u4 
    L_000a: ret 
}

Tôi nghĩ rằng tôi rất vui khi nói rằng đây là lỗi trình biên dịch, hoặc ít nhất là: một tác dụng phụ và hành vi không mong muốn được đưa ra là các quyết định trước đó đã được đưa ra để nói "phát ra .locals init" , đặc biệt là thử và giữ stackalloclành mạnh - nhưng liệu người biên dịch có đồng ý hay không là tùy thuộc vào họ.

Cách giải quyết là: coi stackallockhông gian là không xác định (điều này, công bằng mà nói, là những gì bạn muốn làm); nếu bạn mong đợi nó là số không: bằng tay không.


2
Có vẻ như có một vé mở cho việc này. Tôi sẽ thêm một bình luận mới cho điều đó.
Gyorgy Kőszeg

Huh, tất cả công việc của tôi và tôi đã không nhận thấy đầu tiên đã bị mất locals init. Đẹp một.
cant7

1
@ canton7 nếu bạn là bất cứ ai như tôi, bạn sẽ tự động bỏ qua .maxstack.locals, thật dễ dàng để không nhận thấy rằng nó đang / không có ở đó :)
Marc Gravell

1
The content of the newly allocated memory is undefined.theo MSDN. Các đặc điểm kỹ thuật không nói rằng bộ nhớ nên bằng không. Vì vậy, có vẻ như nó chỉ hoạt động trên khung cũ một cách tình cờ, hoặc là kết quả của một hành vi phi hợp đồng.
Luaan
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.