0

    C:结构体复制与赋值、浅拷贝与深拷贝相关问题

    2023.07.28 | admin | 140次围观

    先思考:

    结构体能否用“=”号直接赋值?

    如何理解结构体的浅拷贝与深拷贝?

    结构体“=”号赋值与“malloc”赋值哪个更好?效率更高?

    直接上代码!

    1.结构体能否用“=”号直接赋值?

    编写C代码:

    vi struck_assign.c
    

    内容如下:

    #include 
    struct Foo {
        char a;
        int b;
        double c;
    }foo1, foo2;          //define two structs with three different fields
    void struct_assign(void)
    {
        foo2 = foo1;       //structure directly assignment
    }
    int main()
    {
        foo1.a = 'a';
        foo1.b = 1;
        foo1.c = 3.14;
    	struct_assign();
        printf("%c %d %lf\n", foo2.a, foo2.b, foo2.c);
        return 0;   
    }
    

    可以从结果上看出,结构体直接赋值在C语言下是可行的。

    汇编C代码:

    gcc -S struck_assign.c
    cat struck_assign.s
    

    我们看看struct_assign()函数的汇编实现,从而从底层看看C语言是如何实现两个结构体之间的赋值操作的:

    struct_assign:
        ...
    	movq	%rsp, %rbp
    	.cfi_def_cfa_register 6
    	movq	foo1(%rip), %rax
    	movq	foo1+8(%rip), %rdx
    	movq	%rax, foo2(%rip)
    	movq	%rdx, foo2+8(%rip)
    	nop
    

    这段汇编比较简单,由于结构体的对齐的特性,sizeof(srtruct Foo)=16,通过四次movl操作将foo1的结构体内容拷贝到结构体foo2中。从汇编上看出,结构体赋值,采用的类似于memcpy这种形式,而不是逐个字段的拷贝。

    2.如何理解结构体的浅拷贝与深拷贝?

    如果结构体中含有其它复杂数据类型呢版权法中私人复制问题研究:从印刷机到互联网,例如数组、指针、结构体等,从上面的汇编实现可以看出,只要两个结构体类型相同,就可以实现赋值,如下例:

    #include 
    #include 
    struct Foo {
        char a;
        int b;
        double c;
        char *p_d;
    }foo1, foo2;
    void struct_shallow_copy(void)
    {
        foo2 = foo1;       //structure directly assignment
    }
    int main()
    {
        char *d = (char *) malloc (4*sizeof(char));
        d[0] = 'b'; d[1] = 'c'; d[2] = 'd'; d[3] = '\0';
        foo1.a = 'a';
        foo1.b = 1;
        foo1.c = 3.14;
        foo1.p_d = d;
    	struct_shallow_copy();
        printf("%c %d %lf %s\n", foo2.a, foo2.b, foo2.c, foo2.p_d);
        printf("%#x %#x\n", foo1.p_d, foo2.p_d);
        return 0;
    }
    

    C:结构体复制与赋值、浅拷贝与深拷贝相关问题

    可以看出结果和我们想象的是一样的。再次验证结构体的赋值,是直接结构体的内存的拷贝!但正是这个问题,如上面的实例,foo1 和 foo2 中p_d 指针都是指向我们申请的一块大小为4个字节的内存区域,这里注意的是版权法中私人复制问题研究:从印刷机到互联网,结构体的拷贝只是浅拷贝,即指针p_d的赋值并不会导致再申请一块内存区域,让foo2的p_d指向它。那么,如果释放掉foo1中的p_d指向的内存,此时foo2中p_d变成野指针,这是对foo2的p_d操作就会出现一些不可预见的问题!

    那如何做成深拷贝?

    如下代码:

    #include 
    #include 
    #include 
    struct Foo {
        char a;
        int b;
        double c;
        char *p_d;
    }foo1, foo2;
    void struct_deep_copy(void)
    {
        foo2.a = foo1.a;
        foo2.b = foo1.b;
        foo2.c = foo1.c;
    	foo2.p_d = (char *) malloc (4*sizeof(char));
        strcpy(foo2.p_d,foo1.p_d);  //Deep copy
    }
    int main()
    {
        char *d = (char *) malloc (4*sizeof(char));
        d[0] = 'b'; d[1] = 'c'; d[2] = 'd'; d[3] = '\0';
        foo1.a = 'a';
        foo1.b = 1;
        foo1.c = 3.14;
        foo1.p_d = d;
    	struct_deep_copy();
        printf("%c %d %lf %s\n", foo2.a, foo2.b, foo2.c, foo2.p_d);
        printf("%#x %#x\n", foo1.p_d, foo2.p_d);
        return 0;
    }
    

    根据上述的结果,得出结论:

    浅拷贝是将一个结构体里面的值完全赋给另一个结构体,当结构体中含有指针变量时,浅拷贝只会拷贝指针所指向的空间地址值,不会自动分配内存(即原指针与拷贝指针都指向同一块内存,一不小心可能犯对同一块动态内存进行多次释放的错误)

    深拷贝可以自动为指针分配内存(即原指针与拷贝指针所指向的内存空间不同,只是内存中存的值相同,可以避免对同一块动态内存进行多次释放的错误)

    如果结构体内无指针变量,浅拷贝与深拷贝效果相同

    3.结构体“=”号赋值与“memcpy”赋值哪个更好?效率更高?

    上代码直接比较,看下汇编阶段,哪个流程更少点。

    #include 
    #include 
    struct Foo {
        char a;
        int b;
        double c;
        char *d;
    }foo1, foo2, foo3;          //define two structs with three different fields
    void struct_assign(void)
    {
        foo2 = foo1;       //structure directly assignment
    }
    void struct_copy(void)
    {   
    	memcpy(&foo3,&foo1,sizeof(struct Foo));  //Structure directly copy
    }
    int main()
    {
        foo1.a = 'a';
        foo1.b = 1;
        foo1.c = 3.14;
        foo1.d = "efg";
    	struct_assign();
        printf("%c %d %lf %s\n", foo2.a, foo2.b, foo2.c, foo2.d);
        printf("%p\n", foo2.d);
        struct_copy();
        printf("%c %d %lf %s\n", foo3.a, foo3.b, foo3.c, foo3.d);
        printf("%p\n", foo3.d);
        return 0;   
    }
    

    编译汇编如下:

    struct_assign:
        ...
    	movq	%rsp, %rbp
    	.cfi_def_cfa_register 6
    	movq	foo1(%rip), %rax
    	movq	%rax, foo2(%rip)
    	movq	foo1+8(%rip), %rax
    	movq	%rax, foo2+8(%rip)
    	movq	foo1+16(%rip), %rax
    	movq	%rax, foo2+16(%rip)
    	nop
    struct_copy:
        ...
    	movq	%rsp, %rbp
    	.cfi_def_cfa_register 6
    	movq	foo1(%rip), %rax
    	movq	%rax, foo3(%rip)
    	movq	foo1+8(%rip), %rax
    	movq	%rax, foo3+8(%rip)
    	movq	foo1+16(%rip), %rax
    	movq	%rax, foo3+16(%rip)
    	nop
    

    根据上述结果,得出结论:

    两种实现,从汇编层面是一致的!

    结构体赋值实现也是调用的memcpy。本质上效率应该是差不多的!

    Ps:比较效率的话最好能够懂一点简单的汇编代码,看看就明白了。

    参考:

    收录于:

    嵌入式软件/BSP开发工程师/Linux驱动工程师/C语言经典笔试面试题大全

    版权声明

    本文仅代表作者观点。
    本文系作者授权发表,未经许可,不得转载。

    发表评论