C 语言和 ARM 汇编 - 14 结构体的内存分配

我们使用结构体的时候,编译器会为结构体分配一段连续的内存,大小是由结构体大小决定。编译器会根据数据对齐的要求,在数据中增加 padding 来提高访问效率。通过一些设置也能设定 padding 的方式。

数据结构体里面的成员是通过偏移地址进行访问。

代码 struct data_struct { int a; int b; }; struct data_struct global_data; int main() { struct data_struct local_data; global_data.a = 10; global_data.b = 15; local_data.a = 25; local_data.b = 20; return 0; }

汇编 sub sp, sp, #8 ldr r3, .L3 movs r2, #10 # global_data.a = global_data[0] str r2, [r3] ldr r3, .L3 movs r2, #15 # global_data.b = global_data[4] str r2, [r3, #4] movs r3, #25 # local_data.a = sp[0] str r3, [sp] movs r3, #20 # local_data.b = sp[4] str r3, [sp, #4] movs r3, #0 mov r0, r3 add sp, sp, #8 @ sp needed bx lr .L4: .align 2 .L3: .word global_data

padding

编译器通常会通过 padding 将结构体成员对齐到整数倍字节,提高访问效率。对齐的字节数根据 CPU 架构会有所不同。

举个例子

struct data_struct
{
    char a;
    int b;
};

如果我们在 i386 上使用 sizeof(struct data_struct) 获得的数值是 8,虽然 char 实际占用的是1个字节,但编译器会对齐到 4字节。

struct data_struct
{
    char a;
    int b;
} __attribute__((packed));

通过设置 packed 可以取消编译器的优化效果,将结构体成员按照实际占用的数值分配。这个属性也可以放到成员上,比如

struct data_struct
{
    char a;
    int b __attribute__((packed));
};

通过函数返回结构体

struct data_struct
{
    int a;
    int b;
} ;
struct data_struct fun()
{
    struct data_struct data;
    data.a = 20;
    data.b = 25;
    return data;
}
int main()
{
    struct data_struct data = fun();
    return 0;
}

汇编

main:
    push    {lr}
    sub sp, sp, #12
    mov r3, sp
# 将堆栈的首地址 sp 作为参数传递给 fun,然后在 fun 里面通过指针偏移操作结
# 构体成员
    mov r0, r3
    bl  fun

实际上对应了这种写法

void fun(struct data_struct *ptr)
{
    struct data_struct data;
    data.a = 20;
    data.b = 25;
    ptr->a = data.a;
    ptr->b = data.b;
}
int main()
{
    struct data_struct data;
    fun(&data);
    return 0;
}

通过指针传递的方式实现了结构体数据返回,有2点需要注意: