c语言如何跟进自定义赋值,C语言结构体赋值分析

C++相比C语言的-大便利是类和结构体可以直接用等号赋值。C++为类和结构体提供了可自定义的赋值操作符opeartor =,甚至编译器会自动生成默认的赋值操作符。如下所示:

struct A {

A(int a = 0) : a_(a)

{

}

int a_;

}

void test()

{

A a(1);

A b = a;

A c;

c = a;

}

虽然知道的人不多,C语言其实也支持结构体的赋值,如下所示:

struct A {

int a;

};

void assign_a(struct A *a, struct A *b)

{

*a = *b;

}

C语言的赋值有一个限制,不支持数组的赋值。C++也有这个限制,所以C++推荐使用STL的vector来代替数组。

C语言的赋值跟C++不同之处在于C语言的赋值操作符不支持用户自定义,只能由编译器生成。

先看一段示例代码:

#define FIXED_LEN 4

struct A {

int a;

char b[FIXED_LEN];

int *p;

int append_len;

char appends[];

};

const int ARRAY_SIZE = 10;

void print_sizeof_a(struct A *a)

{

printf("sizeof A:%lu\n", sizeof(*a));

printf("sizeof member:a=%lu,b=%lu,p=%lu,append_len=%lu\n", sizeof(a->a), sizeof(a->b), sizeof(a->p),

sizeof(a->append_len)/*, sizeof(a->appends)*/);

}

void print_a(struct A *a)

{

int i;

int append_print_len = a->append_len > ARRAY_SIZE ? a->append_len : ARRAY_SIZE;

printf("a=%d,b=[%d,%d,%d,%d],p=%p;append(%d)=", a->a, a->b[0], a->b[1], a->b[2], a->b[3], a->p, a->append_len);

for (i = 0; i < append_print_len; ++i) {

printf("%x ", a->appends[i]);

}

printf("\n");

}

void assign_a(struct A *a, struct A *b)

{

*a = *b;

}

int test(void)

{

const unsigned long size = sizeof(struct A) + ARRAY_SIZE * sizeof(char);

int x = 100;

struct A *a = malloc(size);

a->a = 1;

a->b[0] = 0;

a->b[1] = 2;

a->b[2] = 3;

a->b[3] = 4;

a->p = &x;

a->append_len = ARRAY_SIZE;

memset(a->appends, 0xa, ARRAY_SIZE * sizeof(char));

struct A *b = malloc(size);

memset(b->appends, 0xb, ARRAY_SIZE * sizeof(char));

assign_a(b, a);

print_sizeof_a(a);

printf("a:");

print_a(a);

printf("b:");

print_a(b);

free(a);

free(b);

return 0;

}

用gcc(版本是6.2.0,64位macOS 10.14)编译,并指定以C89标准编译-std=c89。

test函数的输出为:

sizeof A:24

sizeof member:a=4,b=4,p=8,append_len=4

a:a=1,b=[0,2,3,4],p=0x7ffeec85faf4;append(4)=a a a a a a a a a a

b:a=1,b=[0,2,3,4],p=0x7ffeec85faf4;append(4)=a a a a b b b b b b

从输出结果来看,有两个地方要注意:

赋值是浅拷贝。a->p和b->p指向同一个地址。

不支持柔性数组(0长度数组)。a->appends和b->appends并不完全相等,只拷贝了前4个字节。这实际上是编译器生成的赋值操作符的副产品,并不是编译器有意为之。

何出此言?我们先看看assign_a函数的反汇编:

(lldb) dis -n assign_a

struct_assign`assign_a:

: pushq %rbp ; 将调用函数的rbp压栈,保存调用者的rbp,函数返回时再弹出恢复

: movq %rsp, %rbp ; 将rbp设置为rsp,rsp的作用见后面的反汇编分析

: movq %rdi, -0x8(%rbp) ; 将第一个参数a保存到栈上(rbp - 8)

: movq %rsi, -0x10(%rbp) ; 将第二个参数b保存在栈上(rbp - 16)

: movq -0x8(%rbp), %rax ; 将第一个参数a赋值给寄存器rax

: movq -0x10(%rbp), %rdx ; 将第二个参数b赋值给寄存器rdx

: movq (%rdx), %rcx ; 第二个参数b,取指针指向的结构体A的开始64位(对应成员变量a和b)到寄存器rcx中

: movq %rcx, (%rax) ; 将rcx赋值给a指向的结构体A的开始64位

: movq 0x8(%rdx), %rcx ; 取b指向的结构体A的第二个64位(对应成员谜题p)到寄存器rcx

: movq %rcx, 0x8(%rax) ; 将rcx赋值给a指向的结构体的第二个64位

: movq 0x10(%rdx), %rdx ; 取b指向的结构体A的第三个64位(对应成员变量append_len和appends的前4个字节)到寄存器rdx

: movq %rdx, 0x10(%rax) ; 将rdx赋值给a指向的结构体的第三个64位

: nop ; 空指令

: popq %rbp ; 弹出rbp,恢复调用者的rbp

: retq ; 函数返回

从上面分析可知,赋值操作一共拷贝了24个字节,也就是sizeof struct A的大小,编译器把最后4个字节看作是paddings,而不是appends的前4个字节。在编译器看来,appends只是不占空间的符号,所以sizeof struct A不包含appends的大小。实际上sizeof a->appends会报编译错误,因为编译时刻并不能知道柔性数组的长度。

如果将FIXED_LEN变大,编译器生成的赋值操作符也会随之变化。例如,将其改为128,赋值操作符不再用movq指令,而改用memcpy。其原型为:

void *memcpy(void *restrict dst, const void *restrict src, size_t n);

assign_a函数反汇编变为:

(lldb) dis -n assign_a

struct_assign`assign_a:

: pushq %rbp

: movq %rsp, %rbp

: subq $0x10, %rsp ; rsp预留本函数用来保存临时变量的空间,也就是下一级函数的rbp

: movq %rdi, -0x8(%rbp)

: movq %rsi, -0x10(%rbp)

: movq -0x8(%rbp), %rdx

: movq -0x10(%rbp), %rax

: movq %rdx, %rcx

: movl $0x98, %edx ; memcpy第三个参数n(通过寄存器edx传递)

: movq %rax, %rsi ; memcpy第二个参数src(通过寄存器rsi传递)

: movq %rcx, %rdi ; memcpy第一个参数dst(通过寄存器rdi传递)

: callq 0x100000de6 ; symbol stub for: memcpy

: nop

: leave

: retq

总结

结构体赋值的出处:

最早可追溯到K&R经典

gcc实现的C89已经支持

C99规定结构体赋值不包含柔性数组

赋值适用场景:

左值和右值结构体类型相同;

无指针成员变量的结构体;

带指针成员并且指针地址可以共享的结构体。因为赋值操作是浅拷贝,指针成员需要结合使用场景,看是用浅拷贝还是深拷贝。

赋值不适用场景(用memcopy):

数组拷贝;

带柔性数组成员的结构体;

带指针成员并且指针地址不能共享的结构体。

附录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值