計算機(jī)內(nèi)存探秘:物理存儲器、地址空間與內(nèi)存地址
對于初學(xué)者來說,計算機(jī)的“內(nèi)存”概念有時會讓人感到困惑。我們知道程序和數(shù)據(jù)放在內(nèi)存里運(yùn)行,也聽說過“內(nèi)存地址”這個詞,但它到底代表什么?物理內(nèi)存條、顯卡顯存、主板上的ROM...這些都是存儲器,它們是如何被統(tǒng)一管理的?
本文將帶你探索計算機(jī)存儲器的不同層面,理解物理存儲器、存儲地址空間以及程序所感知的“內(nèi)存地址”之間的關(guān)系。
1. 物理存儲器:硬件層面的“倉庫”
首先,我們來談?wù)?strong>物理存儲器 (Physical Storage)。顧名思義,這是指計算機(jī)硬件中實(shí)際存在的、用于存儲數(shù)據(jù)的芯片或設(shè)備。最常見的物理存儲器包括:
- 主內(nèi)存 (Main Memory / RAM): 插在主板上的內(nèi)存條,是CPU主要的工作區(qū)域。
- 顯卡顯存 (VRAM): 位于顯卡上的專用高速存儲器,用于存儲圖形數(shù)據(jù)和紋理。
- 各種適配器上的 ROM 或 RAM: 例如,網(wǎng)卡、聲卡等設(shè)備上也可能有存儲固件(ROM)或少量用于緩沖數(shù)據(jù)的RAM。
這些物理存儲器是分散在計算機(jī)系統(tǒng)中的獨(dú)立硬件單元。它們各自有自己的控制器,以及訪問其內(nèi)部數(shù)據(jù)的機(jī)制。
2. 存儲地址空間 (Per-Device): 各自為政的地址范圍
每個物理存儲設(shè)備都有其內(nèi)部的存儲地址空間 (Storage Address Space)。這指的是該設(shè)備內(nèi)部用來標(biāo)識其存儲單元(通常是字節(jié))的地址范圍。
例如,一個 8GB 的內(nèi)存條,它內(nèi)部可能有從地址 0 到 8GB-1 的存儲單元。一塊顯卡的 4GB 顯存,它內(nèi)部也有從地址 0 到 4GB-1 的存儲單元。一個設(shè)備上的 ROM 可能有從地址 0 到 ROM 大小-1 的地址。
你可以把這想象成不同的建筑物,每棟建筑物里的房間都有從 1 開始編號。建筑物 A 的 1 號房間和建筑物 B 的 1 號房間是完全不同的兩個地方。每個物理設(shè)備就是一棟“建筑物”,它的內(nèi)部地址空間就是這棟建筑物里“房間”的編號范圍。
問題來了:CPU 如何統(tǒng)一管理和訪問這些分散在不同物理設(shè)備、擁有各自獨(dú)立地址空間的存儲單元呢?CPU 不能直接說“請給我建筑物 B 的 1 號房間的東西”。
3. 統(tǒng)一的視圖:內(nèi)存地址空間與線性地址
為了讓 CPU 和軟件能夠方便地訪問和管理這些分散的物理存儲資源,操作系統(tǒng)和硬件(特別是內(nèi)存管理單元 MMU)會將這些物理設(shè)備的地址映射 (Mapping) 到一個統(tǒng)一的、線性的地址空間中。這個統(tǒng)一的地址空間,就是我們通常在討論計算機(jī)系統(tǒng)時所說的內(nèi)存地址空間,或者更精確地說,是線性地址空間 (Linear Address Space),在現(xiàn)代操作系統(tǒng)中,它往往是虛擬地址空間 (Virtual Address Space) 的一部分。
這個過程可以理解為:系統(tǒng)為所有的物理存儲資源(包括 RAM、顯存、各種設(shè)備的寄存器和內(nèi)存等)編制了一個統(tǒng)一的“地圖”。地圖上的每一個地址都對應(yīng)著某個物理設(shè)備上的某個具體的存儲單元。
例如,在一個 32 位系統(tǒng)中,這個統(tǒng)一的線性地址空間通常是從地址 0x00000000
到 0xFFFFFFFF
,總共 2^32 = 4GB 的地址范圍。系統(tǒng)會將 4GB 物理內(nèi)存的地址范圍映射到這個線性地址空間的一部分,將顯存映射到另一部分,將設(shè)備 ROM/RAM 映射到再一部分,等等。
這樣一來,CPU 只需要使用這個統(tǒng)一的線性地址(例如 0x80001234
),系統(tǒng)硬件就會負(fù)責(zé)將這個線性地址翻譯成對應(yīng)的物理設(shè)備上的物理地址(例如“顯卡顯存上的地址 0x101234
”),從而完成數(shù)據(jù)的訪問。
內(nèi)存地址 (Memory Address) 這個概念,在程序開發(fā)者的視角看來,通常指的就是在這個統(tǒng)一的線性/虛擬地址空間中的地址。當(dāng)我們在 C/C++ 中使用指針獲取變量地址時,獲取到的就是這個線性/虛擬地址空間中的地址。
將內(nèi)存抽象成字節(jié)數(shù)組:
從軟件(特別是操作系統(tǒng)和應(yīng)用程序)的角度看,這個統(tǒng)一的內(nèi)存地址空間可以被抽象成一個巨大的、一維的字節(jié)數(shù)組 (Byte Array)。這個數(shù)組的每一個“格子”就是一個字節(jié)(8 bits),并且都有一個唯一的、從 0 開始的編號,這個編號就是該字節(jié)的內(nèi)存地址。
- 地址 0x00000000 對應(yīng)第一個字節(jié)。
- 地址 0x00000001 對應(yīng)第二個字節(jié)。
- ...
- 地址 0xFFFFFFFF 對應(yīng)最后一個字節(jié)(在 32 位系統(tǒng)中)。
不同類型的數(shù)據(jù),如 char
(1 字節(jié)), int
(通常 4 字節(jié)), float
(通常 4 字節(jié)), double
(通常 8 字節(jié)),以及更復(fù)雜的結(jié)構(gòu)體和數(shù)組,它們在內(nèi)存中會占據(jù)連續(xù)的若干個字節(jié)空間。一個變量的地址通常指的就是它所占用的第一個字節(jié)的地址。
4. 代碼示例:窺探程序眼中的內(nèi)存地址
通過一個簡單的 C 語言程序,我們可以直觀地看到變量在程序所感知的這個“內(nèi)存地址空間”中是如何被分配地址的。
#include <stdio.h> // 包含標(biāo)準(zhǔn)輸入輸出庫,用于使用 printf 函數(shù) #include <stddef.h> // 包含 stddef.h 以使用 size_t 類型 int main() { // 聲明不同類型的變量 char my_char = 'A'; // 字符類型,通常占 1 字節(jié) int my_int = 12345; // 整型,通常占 4 字節(jié) float my_float = 3.14f; // 浮點(diǎn)型,通常占 4 字節(jié) double my_double = 2.71828; // 雙精度浮點(diǎn)型,通常占 8 字節(jié) // 聲明一個數(shù)組 int my_array[5] = {10, 20, 30, 40, 50}; // 包含 5 個整型的數(shù)組 // 聲明一個結(jié)構(gòu)體 struct Point { int x; int y; }; struct Point p = {100, 200}; // 結(jié)構(gòu)體變量 // 聲明一個函數(shù) (實(shí)際上是獲取函數(shù)的入口地址) // 注意:函數(shù)地址通常在代碼段,與數(shù)據(jù)段/棧段的地址在不同的內(nèi)存區(qū)域 void (*print_msg)(void) = main; // 獲取 main 函數(shù)的地址 (示例) // 實(shí)際調(diào)用函數(shù)指針的例子: // print_msg(); // 這會嘗試再次執(zhí)行 main 函數(shù),可能會導(dǎo)致棧溢出或其他問題,不建議在實(shí)際代碼中這樣做! // 這里的目的是演示如何獲取函數(shù)地址 // 打印變量的值、地址和占用的字節(jié)數(shù) // %p 用于打印指針的值 (地址),需要轉(zhuǎn)換為 (void*) 類型以保證跨平臺兼容性 // sizeof 運(yùn)算符用于獲取變量或類型占用的字節(jié)數(shù) printf("變量 my_char:\n"); printf(" 值: %c\n", my_char); printf(" 地址: %p\n", (void*)&my_char); printf(" 占用字節(jié)數(shù): %zu\n", sizeof(my_char)); // %zu 用于 size_t 類型 printf("\n變量 my_int:\n"); printf(" 值: %d\n", my_int); printf(" 地址: %p\n", (void*)&my_int); printf(" 占用字節(jié)數(shù): %zu\n", sizeof(my_int)); printf("\n變量 my_float:\n"); printf(" 值: %f\n", my_float); printf(" 地址: %p\n", (void*)&my_float); printf(" 占用字節(jié)數(shù): %zu\n", sizeof(my_float)); printf("\n變量 my_double:\n"); printf(" 值: %lf\n", my_double); // %lf 用于 double 類型 printf(" 地址: %p\n", (void*)&my_double); printf(" 占用字節(jié)數(shù): %zu\n", sizeof(my_double)); printf("\n數(shù)組 my_array:\n"); // 數(shù)組名本身通常代表數(shù)組第一個元素的地址 printf(" 數(shù)組首地址: %p\n", (void*)my_array); printf(" 第一個元素 my_array[0] 的地址: %p\n", (void*)&my_array[0]); printf(" 第二個元素 my_array[1] 的地址: %p\n", (void*)&my_array[1]); printf(" 占用總字節(jié)數(shù): %zu\n", sizeof(my_array)); printf(" 每個元素占字節(jié)數(shù): %zu\n", sizeof(my_array[0])); // 注意觀察相鄰元素地址之間的差異,它等于元素的大小 (這里是 sizeof(int)) printf("\n結(jié)構(gòu)體 p:\n"); printf(" 結(jié)構(gòu)體首地址: %p\n", (void*)&p); printf(" 成員 p.x 的地址: %p\n", (void*)&p.x); printf(" 成員 p.y 的地址: %p\n", (void*)&p.y); printf(" 占用總字節(jié)數(shù): %zu\n", sizeof(p)); // 注意成員地址與結(jié)構(gòu)體首地址的關(guān)系 printf("\n函數(shù) main 的地址:\n"); printf(" 地址: %p\n", (void*)print_msg); // 打印函數(shù)指針的值 return 0; // 程序正常結(jié)束 }
編譯和運(yùn)行:
- 將上述代碼保存為
address_example.c
文件。 - 打開終端或命令提示符。
- 使用 C 編譯器(如 GCC)編譯代碼:
gcc address_example.c -o address_example
- 運(yùn)行生成的可執(zhí)行文件:
./address_example
運(yùn)行結(jié)果示例:
請注意,輸出的內(nèi)存地址是示例值,具體數(shù)值在您的系統(tǒng)上運(yùn)行或每次運(yùn)行時都可能不同,因?yàn)椴僮飨到y(tǒng)會動態(tài)分配內(nèi)存,并且涉及到虛擬內(nèi)存地址。關(guān)鍵在于觀察地址的相對關(guān)系和不同類型占用的字節(jié)數(shù)。
變量 my_char: 值: A 地址: 0x7ffd7533f0b7 占用字節(jié)數(shù): 1 變量 my_int: 值: 12345 地址: 0x7ffd7533f0b0 占用字節(jié)數(shù): 4 變量 my_float: 值: 3.140000 地址: 0x7ffd7533f0ac 占用字節(jié)數(shù): 4 變量 my_double: 值: 2.718280 地址: 0x7ffd7533f0a0 占用字節(jié)數(shù): 8 數(shù)組 my_array: 數(shù)組首地址: 0x7ffd7533f080 第一個元素 my_array[0] 的地址: 0x7ffd7533f080 第二個元素 my_array[1] 的地址: 0x7ffd7533f084 占用總字節(jié)數(shù): 20 每個元素占字節(jié)數(shù): 4 結(jié)構(gòu)體 p: 結(jié)構(gòu)體首地址: 0x7ffd7533f078 成員 p.x 的地址: 0x7ffd7533f078 成員 p.y 的地址: 0x7ffd7533f07c 占用總字節(jié)數(shù): 8 函數(shù) main 的地址: 地址: 0x563e556c4179
結(jié)果分析:
- 每個變量都被分配了一個唯一的地址。這些地址是程序在運(yùn)行時看到的線性/虛擬地址。
sizeof
運(yùn)算符顯示了不同數(shù)據(jù)類型占用的字節(jié)數(shù),這決定了它們在內(nèi)存中占據(jù)的空間大小。- 對于數(shù)組
my_array
,數(shù)組名my_array
的地址與第一個元素&my_array[0]
的地址相同。相鄰元素&my_array[0]
和&my_array[1]
的地址相差 4 個字節(jié) (0x7ffd7533f084 - 0x7ffd7533f080 = 0x4
),正好是一個int
類型的大小,這印證了數(shù)組元素是連續(xù)存儲的。 - 對于結(jié)構(gòu)體
p
,結(jié)構(gòu)體的首地址就是其第一個成員&p.x
的地址。第二個成員&p.y
的地址緊隨其后(或者根據(jù)編譯器的對齊策略有微小的間隔),地址相差 4 個字節(jié) (0x7ffd7533f07c - 0x7ffd7533f078 = 0x4
),正好是p.x
(int
) 的大小,這說明結(jié)構(gòu)體成員也是按順序存儲的。結(jié)構(gòu)體的總大小是其成員大小的總和(加上可能的對齊填充)。 - 函數(shù)
main
也有一個地址,這是函數(shù)代碼在內(nèi)存中的起始位置。函數(shù)的地址通常位于內(nèi)存的不同區(qū)域(代碼段)與變量(數(shù)據(jù)段/棧段)的地址區(qū)分開來。
這個例子清晰地展示了程序如何看待內(nèi)存——一個擁有連續(xù)地址的字節(jié)序列,各種數(shù)據(jù)類型根據(jù)其大小占據(jù)其中的一部分。操作系統(tǒng)和硬件在底層默默地將這些程序可見的地址翻譯成物理設(shè)備上的實(shí)際地址。
到此這篇關(guān)于計算機(jī)內(nèi)存探秘:物理存儲器、地址空間與內(nèi)存地址的文章就介紹到這了,更多相關(guān)計算機(jī)內(nèi)存:物理存儲器、地址空間與內(nèi)存地址內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
有意思的數(shù)據(jù)結(jié)構(gòu)默克樹 Merkle tree應(yīng)用介紹
這篇文章主要為大家介紹了有意思的數(shù)據(jù)結(jié)構(gòu)默克樹 Merkle tree應(yīng)用介紹,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09鴻蒙系統(tǒng)中的Webview技術(shù)使用方法詳解
webView類是View類的一個擴(kuò)展,用來顯示網(wǎng)頁,它不包含任何的網(wǎng)頁瀏覽器的特征,像沒有導(dǎo)航控制和地址欄,使用起來也很方便,這篇文章主要給大家介紹了關(guān)于鴻蒙系統(tǒng)中Webview技術(shù)使用的相關(guān)資料,需要的朋友可以參考下2024-07-07github版本庫使用詳細(xì)圖文教程(命令行及圖形界面版)
今天我們就來學(xué)習(xí)github的使用,我們將用它來管理我們的代碼,你會發(fā)現(xiàn)它的好處的,當(dāng)然是要在本系列教程全部完成之后,所以請緊跟站長的步伐,今天是第一天,我們來學(xué)習(xí)如何在git上建立自己的版本倉庫,并將代碼上傳到倉庫中2015-08-08