Call by Value与Shallow Copy

-- TOC --

C语言的函数调用,参数传递的方式叫call by value,即做一次copy,入参成了函数的局部变量,在函数执行完毕后消失。

很多C语言的函数接口,入参是地址,copy地址没问题,可以访问地址指向的内容,也可以修改,并在函数返回后,让修改保持。

一般情况下,入参会避免struct结构体,因为copy一个struct结构体的cost较高,而且一不小心,会导致shallow copy的问题。

下面是shallow copy的示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


typedef struct abc {
    int a;
    int b;
    char *c;
} abc;


void test(abc b) {
    printf("%p, %s\n", b.c, b.c);
}


int main(void) {
    abc a = {1,2,NULL};
    a.c = (char*)malloc(32);
    memset(a.c, 0, 32);
    memcpy(a.c, "12345", 5);
    printf("%p, %s\n", a.c, a.c);
    test(a);
    free(a.c);
    return 0;
}

test接口入参是struct abc,调用test后,b是a的一个copy,但是b只是copy了a的所有成员,并没有重新malloc一块内存给b.c这个指针,因此,上面示例代码的两次printf,内容完全一样,即a.c和b.c指向了同一块内存。上面代码运行效果如下:

0x16f82a0, 12345
0x16f82a0, 12345

这就是shallow copy。

如果要避免shallow copy,有两个思路:

  1. 将struct内部指向的资源局部化;
  2. 传递struct的指针;

传指针大家见得多了,资源局部化的做法,即每当申明一个struct变量,变量的资源不需要再malloc,可以将上面的代码可以修改成这样:

#include <stdio.h>


typedef struct abc {
    int a;
    int b;
    char c[32];
} abc;


void test(abc b) {
    printf("%p, %s\n", b.c, b.c);
}


int main(void) {
    abc a = {1,2,"12345"};
    printf("%p, %s\n", a.c, a.c);
    test(a);
    return 0;
}

struct abc的成员c是个确定长度的array,在调用test(b)的时候,会将整个array做copy,这样就避免了shallow copy。上面修改后的代码,两行打印出来的地址已经不同了,但因此整体copy的缘故,地址指向的内容完全一样:

0x7ffcc3e84198, 12345
0x7ffcc3e84168, 12345

其实,在C语言中,我很少会见到入参是结构体的情况,一般都是用指向结构体的指针,这样就完全避免了本文所述的问题。但是,本文所述的问题本身,是需要了解的。

这个问题,与C++的copy constructor和copy assignment直接相关!所谓的copy constructor,就是在constructor内,自己做deep copy,以避免shallow copy的问题。copy assignment就是在对象赋值的时候,重载operator=,自己做deep copy。具体请参考:C++ Copy Semantics

本文链接:https://cs.pynote.net/sf/c/202208132/

-- EOF --

-- MORE --