網站首頁 編程語言 正文
了解內存的原理
1、內存是由 Key 和 Value 組成,Key 是內存地址、Value 是存儲的數據;
2、Key:是一個32位長度的二進制數;(64位的程序則是64位長度的二進制)
- > 32位最大值為二進制 ?0111 1111 1111 1111 1111 1111 1111 1111?
- 或十六進制 0x7FFF FFFF,或十進制 2 147 483 647 (2GB) (int.MaxValue);
- > 在C#程序中由 IntPtr 類型進行存儲,常以十六進制數進行交互;
3、Value:則是一個8位長度的二進制數;(所以說計算機只能存儲 0 和 1 就是這原因)
- > 最大值為二進制 1111 1111?,或十六進制 0xFF,或十進制 255;
- > 也就是 1byte 的數據,所以說計算機最小存儲單位為 byte 也正是如此;
4、內存組成結構如下:
- > ?二進制:Key (0111 1111 1111 1111 1111 1111 1111 1111?) = Value (1111 1111)
- > ?十六進制:Key (0x7FFF FFFF) = Value (0xFF)
- > ?十進制:Key (2 147 483 647) = Value (255)
- > ?程序:Key (IntPtr) = Value (byte)
了解指針的原理
1、指針是用于指向一個值類型數據,非想象中的面向過程邏輯、認為第一個讀取后會自動指向下一個,哈哈;
2、如 int 類型的指針,就是將指定內存地址中的數據轉換成 int 數據;
3、由于 int 類型長度為32位(4byte),所以指針讀取數據時會自動取連續4byte的數據來轉換成 int;
- > 如一個 int 類型值為 123456,假設他的內存地址為 IntPtr(0x014245E0),那么他所占用的內存塊則為以下:
- 第1byte:IntPtr(0x014245E0) = byte(0x40)
- 第2byte:IntPtr(0x014245E1) = byte(0xE2)
- 第3byte:IntPtr(0x014245E2) = byte(0x01)
- 第4byte:IntPtr(0x014245E3) = byte(0x00)
- 組成結構為:IntPtr(0x014245E0) = byte[] { 0x40, 0xE2, 0x01, 0x00 }
- > 那么下一個對象則就從 IntPtr(0x014245E4) 開始,如:IntPtr(0x014245E4) = byte[] { 0x00, 0x00, 0x00, 0x00 };
OK,說完原理得開始說代碼了,來個華麗的分割線;
再聲明一下:?
- 1、由于 C# 程序中默認是不允許使用不安全代碼,如內存控制、指針等操作;
- 2、所以關于非安全操作的代碼需要寫在 unsafe 語句塊中;
- 3、另外還需要設置允許使用不安全代碼,如:解決方案 > 選擇項目 > 右鍵 > 屬性 > 生成 > [√] 允許不安全代碼;
1、通過指針修改 值類型 的變量數據
int val = 10;
?
unsafe
{
? ? int* p = &val; ?//&val用于獲取val變量的內存地址,*p為int類型指針、用于間接訪問val變量
?
? ? *p *= *p; ? ? ? //通過指針修改變量值(執行此操作后 val 變量值將會變成 100)
}
2、通過指針修改 引用類型 的變量數據
string val = "ABC";
?
unsafe
{
? ? fixed (char* p = val) ? //fixed用于禁止垃圾回收器重定向可移動的變量,可理解為鎖定引用類型對象
? ? {
? ? ? ? *p = 'D'; ? ? ? ? ? //通過指針修改變量值(執行此操作后 val 變量值將會變成 "DBC")
? ? ? ? p[2] = 'E'; ? ? ? ? //通過指針修改變量值(執行此操作后 val 變量值將會變成 "DBE")
? ? ? ? int* p2 = (int*)p; ?//將char類型的指針轉換成int類型的指針
? ? }
}
3、通過指針修改 數組對象 的成員數據
double[] array = { 0.1, 1.5, 2.3 };
?
unsafe
{
? ? fixed (double* p = &array[2])
? ? {
? ? ? ? *p = 0.2; ? ? ? ? ? //通過指針修改變量值(執行此操作后 array 變量值將會變成{ 0.1, 1.5, 0.2 })
? ? }
}
4、通過指針修改 類對象 的字段數據
User val = new User() { age = 25 };
?
unsafe
{
? ? fixed (int* p = &val.age) ? //fixed用于禁止垃圾回收器重定向可移動的變量,可理解為鎖定引用類型對象
? ? {
? ? ? ? *p = *p + 1; ? ? ? ? ? ?//通過指針修改變量值(執行此操作后 val.age 變量值將會變成 26)
? ? }
}
?
/*
public class User
{
? ? public string name;
? ? public int age;
}
*/
5、通過IntPtr自定義內存地址修改 值類型 數據
char val = 'A';
?
unsafe
{
? ? int valAdd = (int)&val; ? ? ? ? ? ? //獲取val變量的內存地址,并將地址轉換成十進制數
?
? ? //IntPtr address = (IntPtr)123; ? ? //選擇一個內存地址(可以是任何一個變量的內存地址)
? ? IntPtr address = (IntPtr)valAdd; ? ?//選擇一個內存地址(暫使用val變量的內存地址做測試)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
? ? byte* p = (byte*)address; ? ? ? ? ? //將指定的內存地址轉換成byte類型的指針(如果指定的內存地址不可操的話、那操作時則會報異常“嘗試讀取或寫入受保護的內存。這通常指示其他內存已損壞。”)
? ? byte* p2 = (byte*)2147483647; ? ? ? //還可通過十進制的方式選擇內存地址
? ? byte* p3 = (byte*)0x7fffffff; ? ? ? //還可通過十六進制的方式選擇內存地址
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
? ? *p = (byte)'B'; ? ? ? ? ? ? ? ? ? ? //通過指針修改變量值(執行此操作后 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*指針轉換成int*類型的指針 (#需要注意一點:因為都是byte數據、所以不會報轉換失敗異常)
pChar = (char*)p1; ? ? ?//將void*指針轉換成char*類型的指針(#需要注意一點:因為都是byte數據、所以不會報轉換失敗異常)
7、stackalloc 申請內存空間
unsafe
{
? ? int* intBlock = stackalloc int[100];
? ? char* charBlock = stackalloc char[100];
}
8、Marshal 操作內存數據
using System.Runtime.InteropServices;
?
//int length = 1024; ? ? ? ? ? ? ? ?//定義需要申請的內存塊大小(1KB)
int length = 1024 * 1024 * 1024; ? ?//定義需要申請的內存塊大小(1GB)
IntPtr address = Marshal.AllocHGlobal(length); ? ? ? ? ? ? ? ?//從非托管內存中申請內存空間,并返會該內存塊的地址 (單位:字節)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //相當于byte[length]
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //注意:申請內存空間不會立即在任務管理器中顯示內存占用情況
try
{
? ? #region Marshal - 寫入
? ? {
? ? ? ? Marshal.WriteByte(address, 111); ? ? ? ? ? ? ? ? ? ? ?//修改第一個byte中的數據
? ? ? ? Marshal.WriteByte(address, 0, 111); ? ? ? ? ? ? ? ? ? //修改第一個byte中的數據
? ? ? ? Marshal.WriteByte(address, 1, 222); ? ? ? ? ? ? ? ? ? //修改第二個byte中的數據
? ? ? ? Marshal.WriteByte(address, length - 1, 255); ? ? ? ? ?//修改最后一個byte中的數據 (#此處需要注意,如果定義的偏移量超出則會誤修改其他變量的數據)
? ? }
? ? #endregion
?
? ? #region Marshal - 讀取
? ? {
? ? ? ? int offset = length - 1; ? ?//定義讀取最后一個byte的內容
?
? ? ? ? byte buffer0 = Marshal.ReadByte(address); ? ? ? ? ? ? //讀取第一個byte中的數據
? ? ? ? byte buffer1 = Marshal.ReadByte(address, 0); ? ? ? ? ?//讀取第一個byte中的數據
? ? ? ? byte buffer2 = Marshal.ReadByte(address, 1); ? ? ? ? ?//讀取第二個byte中的數據
? ? ? ? byte buffer3 = Marshal.ReadByte(address, length - 1); //讀取最后一個byte中的數據
? ? }
? ? #endregion
?
? ? #region Marshal - 數組數據寫入到目標內存塊中
? ? {
? ? ? ? //source可以是byte[]、也可以是int[]、char[]...
? ? ? ? byte[] source = new byte[] { 1, 2, 3 };
?
? ? ? ? //將source變量的數組數據拷貝到address內存塊中
? ? ? ? Marshal.Copy(source: source,
? ? ? ? ? ? startIndex: 0, ? ? ? ? ?//從source的第一個item開始
? ? ? ? ? ? length: 3, ? ? ? ? ? ? ?//選擇source的3個item
? ? ? ? ? ? destination: address); ?//選擇存儲的目標 (會寫到address內存塊的開頭處)
? ? }
? ? #endregion
?
? ? #region Marshal - 內存塊數據讀取到目標數組中
? ? {
? ? ? ? //dest可以是byte[]、也可以是int[]、char[]...
? ? ? ? byte[] dest = new byte[5];
?
? ? ? ? Marshal.Copy(source: address,
? ? ? ? ? ? destination: dest, ? ? ?//#注意:目標數組不能為空、且需要有足夠的空間可接收數據
? ? ? ? ? ? startIndex: 1, ? ? ? ? ?//從dest數組的第二個item開始
? ? ? ? ? ? length: 3); ? ? ? ? ? ? //將address內存塊的前3個item寫入到dest數組中
? ? }
? ? #endregion
?
? ? unsafe
? ? {
? ? ? ? int[] array = new int[5] { 1, 2, 3, 4, 5 };
?
? ? ? ? int* p = (int*)Marshal.UnsafeAddrOfPinnedArrayElement(array, 1); ? ?//獲取數組第二個item的內存地址、并轉換成int類型的指針
? ? ? ? char* p2 = (char*)Marshal.UnsafeAddrOfPinnedArrayElement(array, 1); //獲取數組第二個item的內存地址、并轉換成char類型的指針
? ? }
}
finally
{
? ? Marshal.FreeHGlobal(address); ? //釋放非托管內存中分配出的內存 (釋放后可立即騰出空間給系統復用)
}
總結
原文鏈接:https://blog.csdn.net/rcr676/article/details/107507777
- 上一篇:沒有了
- 下一篇:沒有了
相關推薦
- 2022-07-12 Android Studio與夜神模擬器進行連接以及連接中出現的問題
- 2022-11-24 redis使用skiplist跳表的原因解析_Redis
- 2022-04-01 使用Git clone代碼失敗的解決方法
- 2022-07-16 windows下的SDL開發環境搭建(基于mingw編譯運行)
- 2022-03-17 淺析.netcore中的Configuration具體使用_實用技巧
- 2023-02-15 刪除docker中沒有被使用的數據卷volume_docker
- 2022-04-20 超全整理visual?studio快捷鍵使用技巧_相關技巧
- 2022-05-23 C#多線程之線程同步_C#教程
- 欄目分類
-
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支