溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

C#指針內(nèi)存控制Marshal內(nèi)存數(shù)據(jù)存儲(chǔ)原理是什么

發(fā)布時(shí)間:2023-02-27 09:49:26 來源:億速云 閱讀:102 作者:iii 欄目:開發(fā)技術(shù)

本篇內(nèi)容介紹了“C#指針內(nèi)存控制Marshal內(nèi)存數(shù)據(jù)存儲(chǔ)原理是什么”的有關(guān)知識(shí),在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

了解內(nèi)存的原理

1、內(nèi)存是由 Key 和 Value 組成,Key 是內(nèi)存地址、Value 是存儲(chǔ)的數(shù)據(jù);

2、Key:是一個(gè)32位長度的二進(jìn)制數(shù);(64位的程序則是64位長度的二進(jìn)制)

  • > 32位最大值為二進(jìn)制 ?0111 1111 1111 1111 1111 1111 1111 1111?

  • 或十六進(jìn)制 0x7FFF FFFF,或十進(jìn)制 2 147 483 647 (2GB) (int.MaxValue);

  • > 在C#程序中由 IntPtr 類型進(jìn)行存儲(chǔ),常以十六進(jìn)制數(shù)進(jìn)行交互;

3、Value:則是一個(gè)8位長度的二進(jìn)制數(shù);(所以說計(jì)算機(jī)只能存儲(chǔ) 0 和 1 就是這原因)

  • > 最大值為二進(jìn)制 1111 1111?,或十六進(jìn)制 0xFF,或十進(jìn)制 255;

  • > 也就是 1byte 的數(shù)據(jù),所以說計(jì)算機(jī)最小存儲(chǔ)單位為 byte 也正是如此;

4、內(nèi)存組成結(jié)構(gòu)如下:

  • >  二進(jìn)制:Key (0111 1111 1111 1111 1111 1111 1111 1111?) = Value (1111 1111)

  • >  十六進(jìn)制:Key (0x7FFF FFFF) = Value (0xFF)

  • >  十進(jìn)制:Key (2 147 483 647) = Value (255)

  • >  程序:Key (IntPtr) = Value (byte)

了解指針的原理

1、指針是用于指向一個(gè)值類型數(shù)據(jù),非想象中的面向過程邏輯、認(rèn)為第一個(gè)讀取后會(huì)自動(dòng)指向下一個(gè),哈哈;

2、如 int 類型的指針,就是將指定內(nèi)存地址中的數(shù)據(jù)轉(zhuǎn)換成 int 數(shù)據(jù);

3、由于 int 類型長度為32位(4byte),所以指針讀取數(shù)據(jù)時(shí)會(huì)自動(dòng)取連續(xù)4byte的數(shù)據(jù)來轉(zhuǎn)換成 int;

  • > 如一個(gè) int 類型值為 123456,假設(shè)他的內(nèi)存地址為 IntPtr(0x014245E0),那么他所占用的內(nèi)存塊則為以下:

  • 第1byte:IntPtr(0x014245E0) = byte(0x40)

  • 第2byte:IntPtr(0x014245E1) = byte(0xE2)

  • 第3byte:IntPtr(0x014245E2) = byte(0x01)

  • 第4byte:IntPtr(0x014245E3) = byte(0x00)

  • 組成結(jié)構(gòu)為:IntPtr(0x014245E0) = byte[] { 0x40, 0xE2, 0x01, 0x00 }

  • > 那么下一個(gè)對象則就從 IntPtr(0x014245E4) 開始,如:IntPtr(0x014245E4) = byte[] { 0x00, 0x00, 0x00, 0x00 };

OK,說完原理得開始說代碼了,來個(gè)華麗的分割線;

再聲明一下: 

  • 1、由于 C# 程序中默認(rèn)是不允許使用不安全代碼,如內(nèi)存控制、指針等操作;

  • 2、所以關(guān)于非安全操作的代碼需要寫在 unsafe 語句塊中;

  • 3、另外還需要設(shè)置允許使用不安全代碼,如:解決方案 > 選擇項(xiàng)目 > 右鍵 > 屬性 > 生成 > [√] 允許不安全代碼;

1、通過指針修改 值類型 的變量數(shù)據(jù)

int val = 10;
 
unsafe
{
    int* p = &val;  //&val用于獲取val變量的內(nèi)存地址,*p為int類型指針、用于間接訪問val變量
 
    *p *= *p;       //通過指針修改變量值(執(zhí)行此操作后 val 變量值將會(huì)變成 100)
}

2、通過指針修改 引用類型 的變量數(shù)據(jù)

string val = "ABC";
 
unsafe
{
    fixed (char* p = val)   //fixed用于禁止垃圾回收器重定向可移動(dòng)的變量,可理解為鎖定引用類型對象
    {
        *p = 'D';           //通過指針修改變量值(執(zhí)行此操作后 val 變量值將會(huì)變成 "DBC")
        p[2] = 'E';         //通過指針修改變量值(執(zhí)行此操作后 val 變量值將會(huì)變成 "DBE")
        int* p2 = (int*)p;  //將char類型的指針轉(zhuǎn)換成int類型的指針
    }
}

3、通過指針修改 數(shù)組對象 的成員數(shù)據(jù)

double[] array = { 0.1, 1.5, 2.3 };
 
unsafe
{
    fixed (double* p = &array[2])
    {
        *p = 0.2;           //通過指針修改變量值(執(zhí)行此操作后 array 變量值將會(huì)變成{ 0.1, 1.5, 0.2 })
    }
}

4、通過指針修改 類對象 的字段數(shù)據(jù)

User val = new User() { age = 25 };
 
unsafe
{
    fixed (int* p = &val.age)   //fixed用于禁止垃圾回收器重定向可移動(dòng)的變量,可理解為鎖定引用類型對象
    {
        *p = *p + 1;            //通過指針修改變量值(執(zhí)行此操作后 val.age 變量值將會(huì)變成 26)
    }
}
 
/*
public class User
{
    public string name;
    public int age;
}
*/

5、通過IntPtr自定義內(nèi)存地址修改 值類型 數(shù)據(jù)

char val = 'A';
 
unsafe
{
    int valAdd = (int)&val;             //獲取val變量的內(nèi)存地址,并將地址轉(zhuǎn)換成十進(jìn)制數(shù)
 
    //IntPtr address = (IntPtr)123;     //選擇一個(gè)內(nèi)存地址(可以是任何一個(gè)變量的內(nèi)存地址)
    IntPtr address = (IntPtr)valAdd;    //選擇一個(gè)內(nèi)存地址(暫使用val變量的內(nèi)存地址做測試)
                                    
    byte* p = (byte*)address;           //將指定的內(nèi)存地址轉(zhuǎn)換成byte類型的指針(如果指定的內(nèi)存地址不可操的話、那操作時(shí)則會(huì)報(bào)異?!皣L試讀取或?qū)懭胧鼙Wo(hù)的內(nèi)存。這通常指示其他內(nèi)存已損壞。”)
    byte* p2 = (byte*)2147483647;       //還可通過十進(jìn)制的方式選擇內(nèi)存地址
    byte* p3 = (byte*)0x7fffffff;       //還可通過十六進(jìn)制的方式選擇內(nèi)存地址
                                        
    *p = (byte)'B';                     //通過指針修改變量值(執(zhí)行此操作后 val 變量值將會(huì)變成 'B')
}

6、void* 一個(gè)任意類型的指針

int valInt = 10;        //定義一個(gè)int類型的測試val
char valChar = 'A';     //定義一個(gè)char類型的測試val
 
int* pInt = &valInt;    //定義一個(gè)int*類型的指針
char* pChar = &valChar; //定義一個(gè)char*類型的指針
 
void* p1 = pInt;        //void*可以用于存儲(chǔ)任意類型的指針
void* p2 = pChar;       //void*可以用于存儲(chǔ)任意類型的指針
 
pInt = (int*)p2;        //將void*指針轉(zhuǎn)換成int*類型的指針 (#需要注意一點(diǎn):因?yàn)槎际莃yte數(shù)據(jù)、所以不會(huì)報(bào)轉(zhuǎn)換失敗異常)
pChar = (char*)p1;      //將void*指針轉(zhuǎn)換成char*類型的指針(#需要注意一點(diǎn):因?yàn)槎际莃yte數(shù)據(jù)、所以不會(huì)報(bào)轉(zhuǎn)換失敗異常)

7、stackalloc 申請內(nèi)存空間

unsafe
{
    int* intBlock = stackalloc int[100];
    char* charBlock = stackalloc char[100];
}

8、Marshal 操作內(nèi)存數(shù)據(jù)

using System.Runtime.InteropServices;
 
//int length = 1024;                //定義需要申請的內(nèi)存塊大小(1KB)
int length = 1024 * 1024 * 1024;    //定義需要申請的內(nèi)存塊大小(1GB)
IntPtr address = Marshal.AllocHGlobal(length);                //從非托管內(nèi)存中申請內(nèi)存空間,并返會(huì)該內(nèi)存塊的地址 (單位:字節(jié))
                                                              //相當(dāng)于byte[length]
                                                              //注意:申請內(nèi)存空間不會(huì)立即在任務(wù)管理器中顯示內(nèi)存占用情況
try
{
    #region Marshal - 寫入
    {
        Marshal.WriteByte(address, 111);                      //修改第一個(gè)byte中的數(shù)據(jù)
        Marshal.WriteByte(address, 0, 111);                   //修改第一個(gè)byte中的數(shù)據(jù)
        Marshal.WriteByte(address, 1, 222);                   //修改第二個(gè)byte中的數(shù)據(jù)
        Marshal.WriteByte(address, length - 1, 255);          //修改最后一個(gè)byte中的數(shù)據(jù) (#此處需要注意,如果定義的偏移量超出則會(huì)誤修改其他變量的數(shù)據(jù))
    }
    #endregion
 
    #region Marshal - 讀取
    {
        int offset = length - 1;    //定義讀取最后一個(gè)byte的內(nèi)容
 
        byte buffer0 = Marshal.ReadByte(address);             //讀取第一個(gè)byte中的數(shù)據(jù)
        byte buffer1 = Marshal.ReadByte(address, 0);          //讀取第一個(gè)byte中的數(shù)據(jù)
        byte buffer2 = Marshal.ReadByte(address, 1);          //讀取第二個(gè)byte中的數(shù)據(jù)
        byte buffer3 = Marshal.ReadByte(address, length - 1); //讀取最后一個(gè)byte中的數(shù)據(jù)
    }
    #endregion
 
    #region Marshal - 數(shù)組數(shù)據(jù)寫入到目標(biāo)內(nèi)存塊中
    {
        //source可以是byte[]、也可以是int[]、char[]...
        byte[] source = new byte[] { 1, 2, 3 };
 
        //將source變量的數(shù)組數(shù)據(jù)拷貝到address內(nèi)存塊中
        Marshal.Copy(source: source,
            startIndex: 0,          //從source的第一個(gè)item開始
            length: 3,              //選擇source的3個(gè)item
            destination: address);  //選擇存儲(chǔ)的目標(biāo) (會(huì)寫到address內(nèi)存塊的開頭處)
    }
    #endregion
 
    #region Marshal - 內(nèi)存塊數(shù)據(jù)讀取到目標(biāo)數(shù)組中
    {
        //dest可以是byte[]、也可以是int[]、char[]...
        byte[] dest = new byte[5];
 
        Marshal.Copy(source: address,
            destination: dest,      //#注意:目標(biāo)數(shù)組不能為空、且需要有足夠的空間可接收數(shù)據(jù)
            startIndex: 1,          //從dest數(shù)組的第二個(gè)item開始
            length: 3);             //將address內(nèi)存塊的前3個(gè)item寫入到dest數(shù)組中
    }
    #endregion
 
    unsafe
    {
        int[] array = new int[5] { 1, 2, 3, 4, 5 };
 
        int* p = (int*)Marshal.UnsafeAddrOfPinnedArrayElement(array, 1);    //獲取數(shù)組第二個(gè)item的內(nèi)存地址、并轉(zhuǎn)換成int類型的指針
        char* p2 = (char*)Marshal.UnsafeAddrOfPinnedArrayElement(array, 1); //獲取數(shù)組第二個(gè)item的內(nèi)存地址、并轉(zhuǎn)換成char類型的指針
    }
}
finally
{
    Marshal.FreeHGlobal(address);   //釋放非托管內(nèi)存中分配出的內(nèi)存 (釋放后可立即騰出空間給系統(tǒng)復(fù)用)
}

“C#指針內(nèi)存控制Marshal內(nèi)存數(shù)據(jù)存儲(chǔ)原理是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI