Cạm bẫy ẩn của fixed buffer trong C#

Kevin Gosse đã sử dụng cong nghe Microsoft .NET trong 20 nam, qua cac ung dung may tram, may chu va di dong. Ong dam me go loi va toi uu hieu nang, la Microsoft MVP va hien lam viec tai JetBrains, noi ong cai thien hieu nang cua ReSharper. Ong cung la dong tac gia cua cuon Pro .NET Memory Management, phien ban thu hai.

Ngay 29 thang 9 nam 2026

Mot ngay toi uu hieu nang binh thuong…

Trong qua trinh toi uu ma nguon xu ly ban quyen cua ReSharper, toi gap mot van de kha thu vi. De co them boi canh, ma xu ly ban quyen duoc viet truoc khi BigInteger duoc them vao .NET Framework, vi vay no su dung mot phien ban ma nguon mo cu, ban dau do Chew Keong Tan viet. Ban goc hien khong con de tim, nhung van co mot vai ban fork o day do.

Ma nay van hoat dong, nhung gay ra mot so van de trong ReSharper vi no khong duoc viet voi muc tieu toi uu hieu nang. Cu the hon, lop nay gan nhu co dang sau:

public class BigInteger<br>{<br>    private const int maxLength = 70;<br>    private uint[] data;<br>    private int dataLength;<br><br>    public BigInteger()<br>    {<br>        data = new uint[maxLength];<br>        dataLength = 1;<br>    }<br><br>    // ...<br>}

Van de nam o dau? Day la mot class, duoc cap phat tren heap, va no boc mot mang uint[] kha lon gom 70 phan tu. Moi phep toan so hoc, nhu cong, nhan hay lay phan du, deu tao ra mot doi tuong BigInteger moi; moi doi tuong lai tiep tuc cap phat mot mang moi. Ma xu ly ban quyen cua ReSharper su dung mat ma hoc, nghia la co rat nhieu phep toan nhu vay. Luong cap phat nhanh chong tang len den muc co the nhin thay ro trong ket qua profiling.

De khac phuc, toi chi can viet lai no thanh mot struct, voi mot mang duoc nhung truc tiep:

public unsafe struct BigInteger<br>{<br>    private const int maxLength = 70;<br>    private fixed uint data[maxLength];<br>    private int dataLength;<br><br>    public BigInteger()<br>    {<br>        dataLength = 1;<br>    }<br><br>    // ...<br>}

Neu ban chua quen voi fixed array, day la tinh nang luu mang noi tuyen, truc tiep ben trong struct.

Trong C# hien dai, ban nen dung thuoc tinh InlineArray thay the. Cach nay an toan va thanh lich hon:

public struct BigInteger<br>{<br>    private const int maxLength = 70;<br>    private Data data;<br>    private int dataLength;<br><br>    public BigInteger()<br>    {<br>        dataLength = 1;<br>    }<br><br>    // ...<br><br>    [InlineArray(maxLength)]<br>    private struct Data<br>    {<br>        private uint _element;<br>    }<br>}

Dang tiec, ReSharper bi gioi han trong .NET Framework vi no chay ben trong Visual Studio, vi vay chung ta van phai dung fixed array.

Ve mat hinh thuc, viec viet lai ma khong qua kho. Tuy nhien, khi kiem thu, toi nhan ra toan bo tinh nang da bi hong. Viec go loi kha kho vi mot ly do don gian: toi khong biet cac ket qua trung gian ma ma mat ma dang le phai tao ra la gi, va den nay van vay. Do do, toi khong co cach nao nhan ra thoi diem ket qua bat dau lech. Dù vay, sau nhieu lan kien tri, toi da thu hep duoc nguyen nhan ve mot hanh vi bat ngo cua fixed array.

Truoc khi tiet lo ngay, hay danh mot phut xem xet doan ma sau:

for (int i = 0; i < 5; i++)<br>{<br>    var a = new StructWithFixedBuffer();<br>    Console.Write($"{a.buffer[0]} ");<br>    a.buffer[0]++;<br>}<br><br>unsafe struct StructWithFixedBuffer<br>{<br>    public fixed long buffer[1];<br>}

Neu doc ky, ban se nhan ra phep tang duoc thuc hien tren mot ban sao struct se bi huy bo, vi the no khong co tac dung. Ket qua se la:

0 0 0 0 0

Bay gio, hay thay doi mot chi tiet rat nho:

for (int i = 0; i < 5; i++)<br>{<br>    var a = new StructWithFixedBuffer();<br>    Console.Write($"{a.buffer[0]} ");<br>    a.buffer[0]++;<br>}<br><br>unsafe struct StructWithFixedBuffer<br>{<br>    public fixed long buffer[1];<br><br>    public StructWithFixedBuffer()<br>    {<br>        // Khong co gi dac biet<br>    }<br>}

Neu ban chua nhan ra, toi da them mot constructor rong vao struct. Do la thay doi duy nhat giua hai phien ban. Chay lai, ket qua se la:

0 1 2 3 4

Dieu dang xay ra la buffer khong con duoc gan gia tri 0 khi tao instance moi. Moi vong lap nhin thay gia tri con lai tu vong lap truoc. Co the ban dang rat boi roi, va toi cung tung nhu vay. Tai sao viec them mot constructor vao struct, du rong hay khong, lai anh huong den viec fixed buffer co duoc gan 0 hay khong? Toi da dua van de len Twitter, nhung Egor Bogatov, mot trong cac ky su JIT cua Microsoft, cung khong co loi giai thich.

Luc can xem xet quy dinh cua C#

Khi gap van de nay mot nam truoc, moi thu khac hon. Toi mo quy dinh C#, tim mot vai tu khoa lien quan nhung khong tim thay dieu gi co the giai thich hanh vi nay, roi ket luan day la mot loi. Mot nam sau, cac agent da lam chu the gioi. Sau khi nho Claude xem ma JIT de giai thich loi, toi moi biet rang hanh vi nay duoc thiet ke nhu vay, va no con dua ra ca bang chung.

Hoa ra, mot vai tinh nang trong C# khien van de kho hieu hon va nguy hiem hon. Truoc tien, hay dua ma ve C# 7.3 bang cach them cac dong sau vao file csproj:

<PropertyGroup><br>    <LangVersion>7.3</LangVersion><br></PropertyGroup>

Luc nay, ma khong con bien dich duoc:

error CS8370: Feature 'parameterless struct constructors' is not available in C# 7.3. Please use language version 10.0 or greater.

Ta sua bang cach them doi so vao constructor. Nhan tien, ta them mot truong de gan gia tri:

for (int i = 0; i < 5; i++)<br>{<br>    var a = new StructWithFixedBuffer(42);<br>    Console.Write($"{a.buffer[0]} ");<br>    a.buffer[0]++;<br>}<br><br>unsafe struct StructWithFixedBuffer<br>{<br>    public fixed long buffer[1];<br>    public int value;<br><br>    public StructWithFixedBuffer(int val)<br>    {<br>        value = val;<br>    }<br>}

Khi chay, ma van in ra 0 1 2 3 4. Bay gio hay chu y mot diem. Neu xoa phep gan cho truong trong constructor:

unsafe struct StructWithFixedBuffer<br>{<br>    public fixed long buffer[1];<br>    public int value;<br><br>    public StructWithFixedBuffer(int val)<br>    {<br>        // Khong co gi<br>    }<br>}

Ta se nhan loi bien dich:

error CS0171: Field 'Program.StructWithFixedBuffer.value' must be fully assigned before control is returned to the caller. Consider updating to language version '11.0' to auto-default the field.

Dung vay, khi viet constructor cho struct, ta phai gan gia tri cho moi truong. Vay tai sao buffer lai khong can? Nguyen nhan nam o mot chi tiet nho trong quy dinh C#, cu the la muc 23.8.4 ve kiem tra gan gia tri day du:

Fixed-size buffer khong chiu su kiem tra gan gia tri day du, va cac thanh vien cua fixed-size buffer bi bo qua khi kiem tra viec gan gia tri day du cho bien kieu struct. Khi bien struct ben ngoai cung chua fixed-size buffer la bien tinh, bien instance cua mot doi tuong class, hoac phan tu mang, cac phan tu cua fixed-size buffer se tu dong duoc khoi tao ve gia tri mac dinh. Trong moi truong hop khac, noi dung ban dau cua fixed-size buffer la khong xac dinh.

“Kiem tra gan gia tri day du” la ten cua tinh nang dam bao moi truong cua struct deu duoc gan trong qua trinh khoi tao. Noi don gian hon: trinh bien dich khong bat buoc ban gan gia tri cho fixed buffer. Neu struct nam tren heap, chang han nhu truong tinh, truong cua class hoac phan tu mang, buffer se duoc gan 0. Neu no la bien cuc bo, moi ket qua deu co the xay ra.

Dieu nay von da nguy hiem trong cac phien ban C# cu, vi ma unsafe thi von nguy hiem. Tuy nhien, cac struct tu dong gan gia tri mac dinh con khien tinh hinh te hon. Tu C# 11, ban khong con phai gan 0 cho moi truong cua struct; trinh bien dich se lam viec do thay ban. Ngoai tru fixed buffer. Vi the, doan ma sau van hop le:

unsafe struct StructWithFixedBuffer<br>{<br>    public fixed long buffer[1];<br>    public int value1;<br>    public int value2;<br><br>    public StructWithFixedBuffer(int val)<br>    {<br>        value1 = val;<br>    }<br>}

Trinh bien dich se gan 0 cho value2 vi truong nay chua duoc gan, nhung se de nguyen buffer.

Con phien ban khong co constructor thi sao?

unsafe struct StructWithFixedBuffer<br>{<br>    public fixed long buffer[1];<br>    public int value1;<br>    public int value2;<br>}

Trong truong hop nay, khi tao struct, trinh bien dich se phat ra lenh initobj. Ve co ban, no tuong duong voi:

var a = default(StructWithFixedBuffer);

Luc do, JIT se gan 0 cho cac truong cua struct, bao gom ca fixed buffer. That la de gay nham lan.

Cach sua

Khi da hieu van de, cach sua kha don gian: yeu cau Microsoft viet lai quy dinh C#, yeu cau Microsoft chuyen Visual Studio sang .NET Core de ReSharper co the dung InlineArray, ma la tu tay gan 0 cho buffer trong constructor.

public unsafe struct BigInteger<br>{<br>    private const int maxLength = 70;<br>    private fixed uint data[maxLength];<br>    private int dataLength;<br><br>    public BigInteger()<br>    {<br>        dataLength = 1;<br>        fixed (uint* d = data)<br>            new Span<uint>(d, maxLength).Clear(); // Dung Span de co vectorization mien phi<br>    }<br><br>    // ...<br>}

Hoac tot hon, neu co the, hay dung InlineArray de khoi phai tu lo voi van de nay.

Nhan: dotnet