C# 指針內(nèi)存控制Marshal內(nèi)存數(shù)據(jù)存儲原理分析
了解內(nèi)存的原理
1、內(nèi)存是由 Key 和 Value 組成,Key 是內(nèi)存地址、Value 是存儲的數(shù)據(jù);
2、Key:是一個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)行存儲,常以十六進(jìn)制數(shù)進(jìn)行交互;
3、Value:則是一個8位長度的二進(jìn)制數(shù);(所以說計算機(jī)只能存儲 0 和 1 就是這原因)
- > 最大值為二進(jìn)制 1111 1111?,或十六進(jìn)制 0xFF,或十進(jìn)制 255;
- > 也就是 1byte 的數(shù)據(jù),所以說計算機(jī)最小存儲單位為 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、指針是用于指向一個值類型數(shù)據(jù),非想象中的面向過程邏輯、認(rèn)為第一個讀取后會自動指向下一個,哈哈;
2、如 int 類型的指針,就是將指定內(nèi)存地址中的數(shù)據(jù)轉(zhuǎn)換成 int 數(shù)據(jù);
3、由于 int 類型長度為32位(4byte),所以指針讀取數(shù)據(jù)時會自動取連續(xù)4byte的數(shù)據(jù)來轉(zhuǎn)換成 int;
- > 如一個 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 }
- > 那么下一個對象則就從 IntPtr(0x014245E4) 開始,如:IntPtr(0x014245E4) = byte[] { 0x00, 0x00, 0x00, 0x00 };
OK,說完原理得開始說代碼了,來個華麗的分割線;
再聲明一下:
- 1、由于 C# 程序中默認(rèn)是不允許使用不安全代碼,如內(nèi)存控制、指針等操作;
- 2、所以關(guān)于非安全操作的代碼需要寫在 unsafe 語句塊中;
- 3、另外還需要設(shè)置允許使用不安全代碼,如:解決方案 > 選擇項目 > 右鍵 > 屬性 > 生成 > [√] 允許不安全代碼;
1、通過指針修改 值類型 的變量數(shù)據(jù)
int val = 10; ? unsafe { ? ? int* p = &val; ?//&val用于獲取val變量的內(nèi)存地址,*p為int類型指針、用于間接訪問val變量 ? ? ? *p *= *p; ? ? ? //通過指針修改變量值(執(zhí)行此操作后 val 變量值將會變成 100) }
2、通過指針修改 引用類型 的變量數(shù)據(jù)
string val = "ABC"; ? unsafe { ? ? fixed (char* p = val) ? //fixed用于禁止垃圾回收器重定向可移動的變量,可理解為鎖定引用類型對象 ? ? { ? ? ? ? *p = 'D'; ? ? ? ? ? //通過指針修改變量值(執(zhí)行此操作后 val 變量值將會變成 "DBC") ? ? ? ? p[2] = 'E'; ? ? ? ? //通過指針修改變量值(執(zhí)行此操作后 val 變量值將會變成 "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 變量值將會變成{ 0.1, 1.5, 0.2 }) ? ? } }
4、通過指針修改 類對象 的字段數(shù)據(jù)
User val = new User() { age = 25 }; ? unsafe { ? ? fixed (int* p = &val.age) ? //fixed用于禁止垃圾回收器重定向可移動的變量,可理解為鎖定引用類型對象 ? ? { ? ? ? ? *p = *p + 1; ? ? ? ? ? ?//通過指針修改變量值(執(zhí)行此操作后 val.age 變量值將會變成 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; ? ? //選擇一個內(nèi)存地址(可以是任何一個變量的內(nèi)存地址) ? ? IntPtr address = (IntPtr)valAdd; ? ?//選擇一個內(nèi)存地址(暫使用val變量的內(nèi)存地址做測試) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ? ? byte* p = (byte*)address; ? ? ? ? ? //將指定的內(nèi)存地址轉(zhuǎn)換成byte類型的指針(如果指定的內(nèi)存地址不可操的話、那操作時則會報異?!皣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 變量值將會變成 'B') }
6、void* 一個任意類型的指針
int valInt = 10; ? ? ? ?//定義一個int類型的測試val char valChar = 'A'; ? ? //定義一個char類型的測試val ? int* pInt = &valInt; ? ?//定義一個int*類型的指針 char* pChar = &valChar; //定義一個char*類型的指針 ? void* p1 = pInt; ? ? ? ?//void*可以用于存儲任意類型的指針 void* p2 = pChar; ? ? ? //void*可以用于存儲任意類型的指針 ? pInt = (int*)p2; ? ? ? ?//將void*指針轉(zhuǎn)換成int*類型的指針 (#需要注意一點(diǎn):因為都是byte數(shù)據(jù)、所以不會報轉(zhuǎn)換失敗異常) pChar = (char*)p1; ? ? ?//將void*指針轉(zhuǎn)換成char*類型的指針(#需要注意一點(diǎn):因為都是byte數(shù)據(jù)、所以不會報轉(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)存空間,并返會該內(nèi)存塊的地址 (單位:字節(jié)) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //相當(dāng)于byte[length] ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //注意:申請內(nèi)存空間不會立即在任務(wù)管理器中顯示內(nèi)存占用情況 try { ? ? #region Marshal - 寫入 ? ? { ? ? ? ? Marshal.WriteByte(address, 111); ? ? ? ? ? ? ? ? ? ? ?//修改第一個byte中的數(shù)據(jù) ? ? ? ? Marshal.WriteByte(address, 0, 111); ? ? ? ? ? ? ? ? ? //修改第一個byte中的數(shù)據(jù) ? ? ? ? Marshal.WriteByte(address, 1, 222); ? ? ? ? ? ? ? ? ? //修改第二個byte中的數(shù)據(jù) ? ? ? ? Marshal.WriteByte(address, length - 1, 255); ? ? ? ? ?//修改最后一個byte中的數(shù)據(jù) (#此處需要注意,如果定義的偏移量超出則會誤修改其他變量的數(shù)據(jù)) ? ? } ? ? #endregion ? ? ? #region Marshal - 讀取 ? ? { ? ? ? ? int offset = length - 1; ? ?//定義讀取最后一個byte的內(nèi)容 ? ? ? ? ? byte buffer0 = Marshal.ReadByte(address); ? ? ? ? ? ? //讀取第一個byte中的數(shù)據(jù) ? ? ? ? byte buffer1 = Marshal.ReadByte(address, 0); ? ? ? ? ?//讀取第一個byte中的數(shù)據(jù) ? ? ? ? byte buffer2 = Marshal.ReadByte(address, 1); ? ? ? ? ?//讀取第二個byte中的數(shù)據(jù) ? ? ? ? byte buffer3 = Marshal.ReadByte(address, length - 1); //讀取最后一個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的第一個item開始 ? ? ? ? ? ? length: 3, ? ? ? ? ? ? ?//選擇source的3個item ? ? ? ? ? ? destination: address); ?//選擇存儲的目標(biāo) (會寫到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ù)組的第二個item開始 ? ? ? ? ? ? length: 3); ? ? ? ? ? ? //將address內(nèi)存塊的前3個item寫入到dest數(shù)組中 ? ? } ? ? #endregion ? ? ? unsafe ? ? { ? ? ? ? int[] array = new int[5] { 1, 2, 3, 4, 5 }; ? ? ? ? ? int* p = (int*)Marshal.UnsafeAddrOfPinnedArrayElement(array, 1); ? ?//獲取數(shù)組第二個item的內(nèi)存地址、并轉(zhuǎn)換成int類型的指針 ? ? ? ? char* p2 = (char*)Marshal.UnsafeAddrOfPinnedArrayElement(array, 1); //獲取數(shù)組第二個item的內(nèi)存地址、并轉(zhuǎn)換成char類型的指針 ? ? } } finally { ? ? Marshal.FreeHGlobal(address); ? //釋放非托管內(nèi)存中分配出的內(nèi)存 (釋放后可立即騰出空間給系統(tǒng)復(fù)用) }
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
c# 在windows中操作IIS設(shè)置FTP服務(wù)器的示例
這篇文章主要介紹了c# 在windows中操作IIS設(shè)置FTP服務(wù)器的示例,幫助大家更好的理解和學(xué)習(xí)使用c#,感興趣的朋友可以了解下2021-03-03Unity UGUI的LayoutElement布局元素組件介紹使用示例
這篇文章主要為大家介紹了Unity UGUI的LayoutElement布局元素組件介紹使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07C#基于Extension Method(擴(kuò)展方法)獲得文件大小的方法
這篇文章主要介紹了C#基于Extension Method(擴(kuò)展方法)獲得文件大小的方法,實例分析了C#擴(kuò)展方法的定義與文件操作的相關(guān)技巧,需要的朋友可以參考下2015-06-06