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)不一样呢?
感谢大家的帮助!
想想指针。如果你有两个相同类型的指针变量,我们称它们为 a
和 b
。如果你做a = b
你做一个浅拷贝,你只复制实际指针b
到a
,而不是b
的内存] 指着。这意味着 a
和 b
都指向相同的内存。
深拷贝会将b
指向的内容复制到一些新分配的内存中。深拷贝会导致 a
和 b
指向不同的内存。
采用你的结构:
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.pointer
和 b.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
),现在 x1
和 x2
都有指针指向为 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 函数,它的堆栈就不再在你手中。
当您从该区域打印值时,您会得到各种各样的东西(您希望得到核心转储,但遗憾的是,情况并非总是如此)。
编辑:修复了评论中提到的错误,问题仍然存在,所以这个问题并不是真正的重复 - 只是我的 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)不一样呢?
感谢大家的帮助!
想想指针。如果你有两个相同类型的指针变量,我们称它们为 a
和 b
。如果你做a = b
你做一个浅拷贝,你只复制实际指针b
到a
,而不是b
的内存] 指着。这意味着 a
和 b
都指向相同的内存。
深拷贝会将b
指向的内容复制到一些新分配的内存中。深拷贝会导致 a
和 b
指向不同的内存。
采用你的结构:
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.pointer
和 b.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
拥有 外部资源 / 例如:
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
),现在 x1
和 x2
都有指针指向为 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 函数,它的堆栈就不再在你手中。
当您从该区域打印值时,您会得到各种各样的东西(您希望得到核心转储,但遗憾的是,情况并非总是如此)。