C 浅拷贝混淆

C Shallow Copy Confusion

编辑:修复了评论中提到的错误,问题仍然存在,所以这个问题并不是真正的重复 - 只是我的 C 编程技能在这一点上不是很好,下面给出了解决方案答案这个问题并没有解决局部变量错误。

警告:对 C 还很陌生

我了解到当我们将一个结构分配给另一个结构时,会执行浅拷贝。但是,我无法理解为什么会这样的结果:

假设下面我尝试初始化一个结构,通过使用赋值运算符将其称为 Type2 结构的 Type1 成员,然后应该执行浅拷贝。这意味着复制了Type2成员的地址:

typedef struct {
    uint8_t someVal;
} Type1

typedef struct {
    Type1 grid[3][3];
} Type2

//Constructor for Type2 "objects"
Type2 Type2_Constructor(void) {
    Type1 empty = {.value = o}
    Type2 newType2;
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++) {
            //Shallow copy empty struct
            newType2.grid[i][j] = empty;
        }
    return newType2;
}

int main (void) {    
    Type2 type2Object = Type2_Constructor();   
    for (int i = 0; i < 3; i ++) 
        for (int j = 0; j < 3; j++){
            printf("%u",type2OBject.grid[i][j].value);
            printf("\n\r%p\n\r",&(type2Object.grid[i][j].value));
            printf("\n\r");
        }   
    return 0;
}

我希望看到:

0
0xMemoryLocation

0
0xMemoryLocation

0
0xMemoryLocation

.
.
.

事实上,我看到的是地址增加 2 个字节的情况:

0
0x7fff57eaca18

0
0x7fff57eaca1a

0
0x7fff57eaca1c

0
0x7fff57eaca1e

0
0x7fff57eaca20

.
.
.

既然浅拷贝应该是直接拷贝地址,为什么&(type2Object.grid[i][j].value)不一样呢?

感谢大家的帮助!

想想指针。如果你有两个相同类型的指针变量,我们称它们为 ab。如果你做a = b你做一个浅拷贝,你只复制实际指针ba,而不是b的内存] 指着。这意味着 ab 都指向相同的内存。

深拷贝会将b指向的内容复制到一些新分配的内存中。深拷贝会导致 ab 指向不同的内存。


采用你的结构:

typedef struct {
    Type1 grid[3][3];
} Type2

如果你有

Type2 a;
Type2 b = { ... };  // Some initialization, not relevant exactly what

然后作业

a = b;   // Copy structure b to a

这是一个浅拷贝。 但是因为结构的数据是一个数组所以整个数组被复制了,它看起来像深拷贝。

如果结构中有指针,则只会复制指针。另一个例子,有一个新的结构:

typedef struct {
    char *pointer;
} Type3;

Type3 a;
Type3 b = { "abcd" };

a = b;

printf("a.pointer = %s\n", a.pointer);
printf("b.pointer = %s\n", b.pointer);

上面的代码将为 a.pointerb.pointer 打印相同的字符串。但它是相同的指针。如果我们添加一个新的指针打印输出:

printf("a.pointer = %p\n", (void *) a.pointer);
printf("b.pointer = %p\n", (void *) b.pointer);

以上两行在赋值后会打印相同的值。两个指针都指向同一个内存。是浅拷贝。

此外,像上面显示的那样的结构分配与执行例如

没有什么不同
memcpy(&a, &b, sizeof a);

实际上我认为您的困惑是您认为引用另一个结构就像在 Java 或 C# 或 C++ 中引用,但实际上并非如此。

Type2 结构中 grid 数组中的每个元素都是 Type1 结构的唯一实例。而且作为独特的实例,它们都占用不同的内存,导致你打印的地址都不一样。

当你这样做时

newType2.grid[i][j] = empty;

您将 empty 结构实例的内容复制到结构实例 newType2.grid[i][j] 中。使用上面显示的 memcpy 调用,赋值所做的实际上是

memcpy(&newType2.grid[i][j], &empty, sizeof newType2.grid[i][j]);

它按位复制 empty 的内容。


关于指针和数组之间的区别,请考虑以下定义:

int a[] = { 1, 2, 3, 4 };
int *p = a;

在记忆中它看起来像这样:

+---+---+---+---+
| 1 | 2 | 3 | 4 |
+---+---+---+---+
^
|
+---+     
| p |
+---+

也就是说,数组及其内容占用 space 足以容纳四个 int 值。然后你有指针 p 指向数组中的第一个元素(数组衰减到指向它们第一个元素的指针,在上面的数组 a 的情况下,使用 a 作为指针等于 &a[0]).

您似乎误解了 shallow/deep 副本是什么。浅拷贝会复制每个成员(或者如果你愿意,可以按位复制)。深拷贝还会复制结构的拥有的资源

让我们从一个简单的例子开始:

struct X {
   int val;
};

X x1 = {24};
X x2 = {42};

x1 = x2;

是的,内置赋值运算符 operator = 确实执行浅拷贝。这意味着以上等同于:

x1.val = x2.val;

在这种情况下,由于 X 它不拥有任何外部资源,所以它也被认为是深拷贝,因为 x2 拥有的所有内容都被复制到x1。这是 X 不拥有任何外部资源这一事实的结果。

X 拥有 外部资源 / 例如:

时,shallow/deep 副本的重要性开始发挥作用
struct X {
   int *my_very_own_external_value;
};

X x1, x2;
x1.my_very_own_external_value = malloc(sizeof(int));
*x1.my_very_own_external_value = 24;

x2.my_very_own_external_value = malloc(sizeof(int));
*x2.my_very_own_external_value = 42;

浅拷贝是这样的:

x1 = x2;

这相当于:

x1.my_very_own_external_value  = x2.my_very_own_external_value;

如果你理解指针意味着 x1 丢失了它分配的内存地址(持有 24),现在 x1x2 都有指针指向为 x2 分配的内存,即存储 42;

的内存

如果你想要一个深拷贝,你需要写:

free(x1.my_very_own_external_value);
x1.my_very_own_external_value  = malloc(sizeof(int));
*x1.my_very_own_external_value  = *x2.my_very_own_external_value;

现在您已经复制了 x2 外部拥有的内存(外部资源)。


免责声明:我可能是用 C++ 风格编写的

您的代码有错误:

//Constructor for Type2 "objects"
Type2 * Type2_Constructor(void) {
    Type1 empty = ...;
    Type2 newType2;
    ... do something ...
    Type2 * newType2Ptr = &newType2;
    return newType2Ptr;
}

newType2 是一个局部变量 - 一个在堆栈上定义的结构。 然后您将其地址返回给调用函数。 一旦你退出 Type2_Constructor 函数,它的堆栈就不再在你手中。

当您从该区域打印值时,您会得到各种各样的东西(您希望得到核心转储,但遗憾的是,情况并非总是如此)。