C语言指针+偏移的计算陷阱
自己在写加载器的时候,通过Elf header中的e_shoff可以找到section header table,当时是这么写的:
Elf64_Shdr* sec_tab_header;
sec_tab_header = info.hdr + info.hdr->e_shoff; //info.hdr是指向Elf Header的指针
info.e_shoff是section header table距离Elf header的偏移,是以字节为单位。当时写的时候觉得没有问题,但是实际上问题很大!!!我们可以举个简单的例子:
struct student {
char name[32];
int age;
};
int main()
{
struct student students[10];
struct student* Tom = students + 3;
return 0;
}
students是个结构体数组,把students加上3的偏移赋值给指针Tom。那么这里的students + 3是哪个地址呢?我们通过反汇编看下:
struct student students[10];
struct student* Tom = students + 3;
1167: 48 8d 85 90 fe ff ff lea -0x170(%rbp),%rax
116e: 48 83 c0 6c add $0x6c,%rax
1172: 48 89 85 88 fe ff ff mov %rax,-0x178(%rbp)
return 0;
lea -0x170(%rbp),%rax:是从栈上分配空间给students变量,因为16字节对齐,所以需要分配0x170个字节(十进制为368,满足16字节对齐要求)。然后把地址存放到rax寄存器中。
add $0x6c,%rax:rax寄存器 的值加上0x6c(十进制为108)。student结构体的大小为36字节。所以students + 3实际上是students addr + 3 * sizeof(struct student)。
mov %rax,-0x178(%rbp):把上一步计算的地址赋值给Tom变量。
所以以后在使用指针+offset的方法计算变量地址的时候需要格外小心。
回到前面的问题,要正确获取到section header table的地址应该这么写:
Elf64_Shdr* sec_tab_header;
sec_tab_header = ((char*)info.hdr + info.hdr->e_shoff);
students + 3等价于students[3]。我们可以善用这种指针转换加偏移的方式,例如:
int i2c_reg_write32(char *i2cdev, uint8_t addr, uint8_t reg, uint32_t val)
{
uint8_t data[5];
data[0] = reg;
data[1] = val & 0xFF;
data[2] = (val >> 8) & 0xFF;
data[3] = (val >> 16) & 0xFF;
data[4] = (val >> 24) & 0xFF;
if (5 == i2c_write(i2cdev, addr, data, 5))
return 0;
return -1;
}
上面的这个写法实际上有更简洁的方法:
int i2c_reg_write32(char *i2cdev, uint8_t addr, uint8_t reg, uint32_t val)
{
uint8_t data[5];
data[0] = reg;
((int*)(data + 1)) = val;
if (5 == i2c_write(i2cdev, addr, data, 5))
return 0;
return -1;
}

浙公网安备 33010602011771号