閑談.Net類型之public的不public,fixed的不能fixed


以前寫過《值類型不是值類型》一文。今天,就再來個語言游戲:public 的不public,fixed 的不能 fixed。本文將構造一個古怪類型:public字段無法訪問,標了fixed關鍵字卻無法fixed。

(1)從 fixed 說起

fixed 的經典用法是取分配到托管堆上的值類型或值類型數組的地址,如:

    public class Test
    {
        private int Value;
        private Int32[] Array = new Int32[3];
        public unsafe void Foo()
        {
            fixed (int* p = &Value)
            {
                *p = 30;
            }

            fixed (Int32* pArray = Array)
            {
                pArray[0] = 30;
            }
        }
    }

為什么要fixed呢?山不轉水轉,有GC存在,隨時可能把它所占的內存搬位置。fixed 就是告訴GC,這一塊內存正被指針操作,別動。

下面是 fixed 的另一種用法。在C中,我們可以在 struct 中嵌入數組,如:

typedef struct S
{
    int buffer[4];
} S;

在C#下,如果像下面這個寫法,所得到的 buffer 實際上是在托管堆上,S本身只存儲了到buffer 的一個引用。

struct S
{
     public int[] buffer = new int[3];
}

要想實現和C一樣在值類型內創建固定大小的內存,就必須用 fixed 關鍵字:

    unsafe struct S
    {
        public fixed int buffer[3];
    }

上面的 buffer 就是一個指針了。假設有 S 的實例s,當在托管堆上時,要使用 s.buffer 就必須先將 s fixed,如果不是在托管堆上,就不用fixed,參見代碼:

   public unsafe class Example
    {
        S field = new S();

        private bool example1()
        {
            // ERROR
            return field.buffer[2] == 0;
        }

        private bool example2()
        {
            // OK
            fixed (S* p = &field)
            {
                return (p->buffer[2] == 0);
            }
        }

        private S example1()
        {
            // OK
            S s = new S();
            s.buffer[2] = 0;
            return s;
        }
    }

遺憾的是,上面fixed的用法(在值類型內創建固定大小的內存)只支持幾個基本類型:bool、byte、 char、 short、int、long、sbyte、ushort、uint、ulong、float 或 double。

如果想使用其它值類型,怎么辦呢?

通過反編譯我們看到這里的fixed實際是個語法糖:

wps_clip_image-6364

wps_clip_image-2592

下面,我們來模擬它,在值類型內部創建一個fixed不支持的類型的內存塊。

    unsafe struct S2
    {
        public Block block;

        [StructLayout(LayoutKind.Sequential, Size = 24)]
        public struct Block
        {
            public Rgb24 Val0;
        }
    }

    public unsafe class S2Example
    {
        S2 S = new S2();

        private void example1()
        {
            fixed(S2* p = &S)
            {
                (&(p->block.Val0))[2].Blue = 25;
            }
        }
    }

(2)托管類型

通過對 fixed 的研究,發現了一個很有趣的事情。我以前一直以為,只要是值類型都可以用指針指:在托管堆里的fixed一下即可。今天發現不是這樣的,有一些值類型是無法使用指針指的。

什么類型呢?屬於托管類型(managed type)的值類型。

.net 里的類型可分為值類型和引用類型,也可以分為托管類型和非托管類型。這兩種說法有什么區別呢?我畫個圖來表示:

image

引用類型一定是托管類型,但是值類型不一定是非托管類型。只有非托管類型才能用 unsafe 指針操作,托管類型不能。

那么,屬於托管類型的值類型有哪些呢?

目前我只發現了兩種:

(1)泛型值類型!

(2)字段或字段的字段或字段的字段的字段(……)是托管類型!

例如:

    public struct Size<T> where T : struct
    {
        public T Width;
        public T Height;

        public Size(T width, T height)
        {
            Width = width;
            Height = height;
        }

        public static Boolean operator ==(Size<T> lhs, Size<T> rhs)
        {
            return lhs.Equals(rhs);
        }

        public static Boolean operator !=(Size<T> lhs, Size<T> rhs)
        {
            return !lhs.Equals(rhs);
        }
    }

它是值類型,但是它是在運行期建立起來的類型,在編譯時它是不存在的!這樣的類型無法用指針操作。

    public unsafe class SizeTExample
    {
        private void example1()
        {
            Size<Int32> size = new Size<Int32>();
            //Error
            Size<Int32>* p = &size;
            p->Width = 200;
        }
    }

(3)Public的不public,fixed 的 不 fixed

有了上面的討論,下面,我們來構造一個奇怪的類型:

    unsafe struct S3
    {
        public Size<Int32> size;
        public fixed byte buffer[4];
    }

這個類型有個字段size是泛型值類型 Size<Int32>,這就導致了S3本身是值類型,也是托管類型。是托管類型的話,就不能進行指針操作等unsafe操作。無法fixed,無法 sizeof等等。無法fixed的話,它標志為 fixed byte 的 buffer 就無法訪問。

    public unsafe class S3Example
    {
        private void example1()
        {
            S3 S = new S3();
            //Error
            fixed (Size<Int32>* p = &size)
            {
            }
        }
    }

這樣的話,我們就成功構造出一個有public字段但是無法訪問,有fixed內存區但是無法fixed的古怪類型。

當然,還有更簡單的做法:

    unsafe struct S4
    {
        public Object obj;
        public fixed byte buffer[4];
    }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM