關於#
為了避免誤會,我很少使用類似這樣的語句
uint32_t *p;
p++;
p+=4;
而是僅使用這樣的形式
uint32_t *p;
uint32_t d;
d = p[4];
uint32_t *pt;
pt = &p[4];
但是在遍歷推演時,後一個形式一定需要一個新變量進行遍歷。
而前一個形式則可以在函數傳參指針的基礎上進行,O0 下一定會比後一個形式節省堆棧。
最近為了提升我的庫性能,決定犧牲一些可讀性,改為不直觀的寫法,所以有後續的測試進行。
結論放在前面,當指針 + 這個動作發生時,被 + 處理的指針類型決定了實際推進了多少 RAM 地址,直觀見下表
指針類型 | RAM 地址推進量 |
---|---|
void* + 1 | 1 |
uint8_t* + 1 | 1 |
uint16_t* + 1 | 2 |
uint32_t* + 1 | 4 |
其他及結構體指針 + 1 | 依據類型大小 |
地址成數值#
使用如下代碼進行簡單測試
#include <stdio.h>
#include <stdint.h>
#include <string.h>
uint8_t u8t[] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
uint16_t u16t[] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
uint32_t u32t[] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
/* 入口 */
int main(int argc, char *argv[])
{
uint8_t p8get;
uint16_t p16get;
uint32_t p32get;
p8get = u8t[1]; // = 1
p8get = *(uint8_t *)(u8t + 1); // = 1
p8get = u8t[2]; // = 2
p8get = *(uint8_t *)(u8t + 2); // = 2
p8get = u8t[3]; // = 3
p8get = *(uint8_t *)(u8t + 3); // = 3
p16get = u16t[1]; // = 1
p16get = *(uint16_t *)(u16t + 1); // = 1
p16get = u16t[2]; // = 2
p16get = *(uint16_t *)(u16t + 2); // = 2
p16get = u16t[3]; // = 3
p16get = *(uint16_t *)(u16t + 3); // = 3
p32get = u32t[1]; // = 1
p32get = *(uint32_t *)(u32t + 1); // = 1
p32get = u32t[2]; // = 2
p32get = *(uint32_t *)(u32t + 2); // = 2
p32get = u32t[3]; // = 3
p32get = *(uint32_t *)(u32t + 3); // = 3
return 0;
}
當然,非常符合預期。例如指針 u32t,+3 就是訪問第三個 buffer 值,非常合理.......嗎?
依據眾所周知對 RAM 的理解,一個地址存一個字節,那麼u8t+1
是推進了 1 個字節 u32t+1
則會推進 4 個字節,+1
這個操作產生了不同的結果。
地址的數值#
驗證以上,使用了如下代碼進行簡單測試
#include <stdio.h>
#include <stdint.h>
#include <string.h>
uint8_t u8t[] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
uint16_t u16t[] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
uint32_t u32t[] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
/* 入口 */
int main(int argc, char *argv[])
{
uint8_t *p8get;
uint16_t *p16get;
uint32_t *p32get;
p8get = u8t; // = 0x7FF6757b4000 <u8t>
p16get = u16t; // = 0x7FF6757b4010 <u16t>
p32get = u32t; // = 0x7FF6757b4040 <u32t>
p8get = u8t + 1; // = 0x7FF6757b4001 <u8t+1>
p16get = u16t + 1; // = 0x7FF6757b4012 <u16t+2>
p32get = u32t + 1; // = 0x7FF6757b4044 <u32t+4>
p8get = u8t + 2; // = 0x7FF6757b4002 <u8t+2>
p16get = u16t + 2; // = 0x7FF6757b4014 <u16t+4>
p32get = u32t + 2; // = 0x7FF6757b4048 <u32t+8>
return 0;
}
可以看出,在指針代表的實際 RAM 地址上,其數值的推進確實是不一樣的,哪怕使用的是相同的 +1
是 void*#
我們很多的函數,作為通用工具,是傳入 void* 的,根據系統狀態、標誌條件的不同,將其作為不同的指針進行判定,那麼有如下測試。
#include <stdio.h>
#include <stdint.h>
#include <string.h>
uint8_t u8t[] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
uint16_t u16t[] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
uint32_t u32t[] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
/* 入口 */
int main(int argc, char *argv[])
{
void *pVget;
uint64_t pVV;
pVget = (void *)u8t; // = 0x7FF61F184000 <u8t>
pVget++;
pVV = (uint64_t)pVget; // = 0x7FF61F184001 <u8t+1>
pVget = (void *)u16t; // = 0x7FF61F184010 <u16t>
pVget++;
pVV = (uint64_t)pVget; // = 0x7FF61F184011 <u16t+1>
pVget = (void *)u32t; // = 0x7FF61F184040 <u32t>
pVget++;
pVV = (uint64_t)pVget; // 0x7FF61F184041 <u32t+1>
return 0;
}
可以看到 void* 與實際 RAM 的行為模式完全一致,+n 就是 + n。
假裝不是 void*#
如果將 void* 作為不同的類型指針進行解析,有如下測試
#include <stdio.h>
#include <stdint.h>
#include <string.h>
uint8_t u8t[] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
uint16_t u16t[] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
uint32_t u32t[] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
/* 入口 */
int main(int argc, char *argv[])
{
void *pVget;
pVget = u8t; // = 0x7FF69FF64000 <u8t>
pVget = (uint8_t *)pVget + 1; // = 0x7FF69FF64001 <u8t+1>
pVget = u8t; // = 0x7FF69FF64000 <u8t>
pVget = (uint8_t *)(pVget + 1); // = 0x7FF69FF64001 <u8t+1>
pVget = u16t; // = 0x7FF69FF64010 <u16t>
pVget = (uint16_t *)pVget + 1; // = 0x7FF69FF64012 <u16t+2>
pVget = u16t; // = 0x7FF69FF64010 <u16t>
pVget = (uint16_t *)(pVget + 1); // = 0x7FF69FF64011 <u16t+1>
pVget = u32t; // 0x7FF69FF64040 <u32t>
pVget = (uint32_t *)pVget + 1; // 0x7FF69FF64044 <u32t+4>
pVget = u32t; // 0x7FF69FF64040 <u32t>
pVget = (uint32_t *)(pVget + 1); // 0x7FF69FF64041 <u32t+1>
return 0;
}
可以看到,當 + 這個動作發生時,被 + 處理的類型決定了實際推進了多少 RAM 地址
此文由 Mix Space 同步更新至 xLog
原始鏈接為 https://www.yono233.cn/posts/shoot/24_7_27_C