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_shoffsection 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;
}
posted @ 2025-12-02 14:23  cockpunctual  阅读(14)  评论(0)    收藏  举报