char*:每次讀寫 1 字節,p+1 偏移 1 字節。int*:每次讀寫 4 字節(32 位),p+1 偏移 4 字節。假設 32 位系統,int printf(const char *fmt, ...),調用 printf("%d %d", var2, var3)。棧佈局(高地址→低地址):
[fmt指針] [var2] [var3] ← 參數壓棧順序(右到左) ↑ fmt (char*)
char *fmt_ptr = fmt; // fmt 指向格式字符串
fmt_ptr++; // 指向格式串下一字符
char *arg_ptr = (char *)&fmt; // 取 fmt 自己的地址(指向棧上 fmt 參數槽)
arg_ptr += 4; // 跳過 fmt 自身 4 字節,指向 var2
int *var2_ptr = (int *)arg_ptr; // 以 int* 讀取 4 字節 → var2
arg_ptr += 4; // 指向 var3
int var3 = *(int *)arg_ptr; // 讀取 var3
(char*)&fmt + 4:以字節為單位偏移,精確定位到下一個參數槽。(int*)((char*)&fmt + 4):將字節指針重新解讀為 int 指針,一次性讀取 4 字節。(int*)p2 - (int*)p1 為相差的 int 個數,與字節無關。
#include
int main() {
int a = 6336; // 0x18C0 = 0001 1000 1100 0000
char *b = (char *)&a; // 強制視為字節數組
printf("%d\n", *b); // 讀取低地址字節:0xC0 = 192 (有符號 char 為 -64)
// 小端機:低位在低地址,a=0x18C0 → 內存: C0 18 00 00
// *b 讀取 C0 → 有符號 char 為 -64
return 0;
}
輸出 -64 證明:char* 只讀取 1 字節,按 signed char 解讀(實現定義)。
| 場景 | 代碼模式 | 原因 |
|---|---|---|
| 異構數據訪問 | char *p = (char*)&struct_var; | 按字節遍歷結構體(如序列化、校驗和) |
| 硬件寄存器映射 | volatile uint32_t *reg = (uint32_t*)0x40000000; | 將物理地址轉為類型化指針 |
| 可變參數解析 | va_list 內部實現 | 按參數類型逐個從棧/寄存器提取 |
| 類型雙關 | float f; uint32_t bits = *(uint32_t*)&f; | 位模式重構(需遵守嚴格別名規則) |
| void* 通用指針還原 | MyStruct *p = (MyStruct*)void_ptr; | C 語言無模板,通用容器/回調必經之路 |
C 標準規定:對象只能通過其有效類型、兼容類型、字符類型(char*/unsigned char*/std::byte*)訪問。違反導致未定義行為,編譯器可假設不同類型指針不指向同一內存進行優化。
// ❌ 違反嚴格別名(某些優化下失效)
float f = 1.0f;
uint32_t bits = *(uint32_t*)&f;
// ✅ 使用 union(C99 保證有效)
union { float f; uint32_t u; } converter;
converter.f = 1.0f;
uint32_t bits = converter.u;
// ✅ 使用 memcpy(標準庫保證無別名問題)
float f = 1.0f;
uint32_t bits;
memcpy(&bits, &f, sizeof(bits));
void* + 文檔約定。unsigned char* 或 std::byte* (C++17) 明確表達「按字節訪問」意圖。int*、double*)前,確保地址滿足對齊要求,否則未定義行為。