关于 C语言 指针的推进量
关于
为了避免误会,我很少使用类似这样的语句
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 地址
特别的
对于 DSP 芯片,一个字节是 16 位,所以 (uint32_t *) 类型 +1 动作,推进是 2 个地址。