在C语言中将一个结构体分配给另一个结构体

181

您能够将一个结构体的实例赋给另一个实例吗,像这样:

struct Test t1;
struct Test t2;
t2 = t1;

我曾看到它用于简单结构,但对于复杂结构是否有效呢?
编译器如何根据数据项的类型来复制数据,即区分int和字符串之间的区别?

6个回答

190

如果结构体类型相同,则是可以的。可以把它想象成内存复制。


87
请记住,没有深拷贝,指向内存的指针并没有被复制。 - Georg Schölly
3
这里也涉及并发问题。 - Tim Post
23
@Tim 并发性对于内置类型的赋值和整数、双精度浮点数等没有任何问题,因为这些类型的赋值操作同样也不是原子操作。 - anon
@Neil 抱歉,它可能是可以的,但这不是这个问题的讨论重点。类型通常是用户定义的,而不是内置的。 - Tim Post
3
好的,如果有创建的副本,我可以使用free()函数释放内存吗? - Betlista
9
由于这些变量是自动变量,所以您无法使用free()释放它们占用的内存:http://en.wikipedia.org/wiki/Automatic_variable - joshdoe

165

是的,结构体支持赋值。但是,存在一些问题:

struct S {
   char * p;
};

struct S s1, s2;
s1.p = malloc(100);
s2 = s1;

现在两个结构体的指针都指向同一块内存 - 编译器不会复制指向的数据。现在很难知道哪个结构体实例拥有这些数据。这就是为什么C++发明了用户可定义赋值运算符的概念 - 您可以编写特定的代码来处理这种情况。

2
我点赞了它,因为阅读它让我意识到了我自己答案中的错误/遗漏。 - Clifford
1
+1 表示注意到实际上没有进行任何复制。 - Tom Duckering
19
为什么这被标记为垃圾邮件?有人失去了鼠标控制吗? - Georg Fritzsche
只是根据我的理解要注意一件事...如果结构体有其他成员变量,例如**int i;**,那么会发生复制操作,因为在这种情况下会有东西需要复制。如果我错了,请纠正我。 - doc_id
4
@rahmanisback,anon 的回答已经很清楚地解释了这个话题:“编译器不会复制所指向的数据”。结构体本身的数据显然是被复制的。 - Tobias
显示剩余2条评论

35

首先看这个例子:

一个简单C程序的C代码如下所示

struct Foo {
    char a;
    int b;
    double c;
} foo1, foo2;

void foo_assign(void)
{
    foo1 = foo2;
}

int main(/*char *argv[],int argc*/)
{
    foo_assign();
    return 0;
}

foo_assign()的等效ASM代码为:

00401050 <_foo_assign>:
  401050:   55                      push   %ebp
  401051:   89 e5                   mov    %esp,%ebp
  401053:   a1 20 20 40 00          mov    0x402020,%eax
  401058:   a3 30 20 40 00          mov    %eax,0x402030
  40105d:   a1 24 20 40 00          mov    0x402024,%eax
  401062:   a3 34 20 40 00          mov    %eax,0x402034
  401067:   a1 28 20 40 00          mov    0x402028,%eax
  40106c:   a3 38 20 40 00          mov    %eax,0x402038
  401071:   a1 2c 20 40 00          mov    0x40202c,%eax
  401076:   a3 3c 20 40 00          mov    %eax,0x40203c
  40107b:   5d                      pop    %ebp
  40107c:   c3                      ret    

正如您所看到的,赋值操作在汇编中只是简单地被替换为一个“mov”指令,赋值运算符意味着将数据从一个内存位置移动到另一个内存位置。

赋值只对结构体的直接成员起作用,并且在结构体中具有复杂数据类型时会失败。其中,“复杂”意味着您不能拥有指向列表的指针数组。

在结构体内部的字符数组本身在大多数编译器上都无法工作,这是因为赋值将尝试简单地复制,甚至不考虑数据类型是否为复杂类型。


2
你能详细说明一下它会在哪些条件下失败吗?因为它似乎总是对我有效。 - AlphaGoku
提升这个回答的唯一原因是它非常有教育意义。谢谢! - William Martens

19

这只是一个简单的复制,就像你使用memcpy()一样(事实上,有些编译器实际上会为该代码生成memcpy()调用)。在C中没有“字符串”,只有指向一堆字符的指针。如果您的源结构包含这样一个指针,那么将复制该指针,而不是字符本身。


2
好的,编译器将其转换为memcpy,请参见此处:https://godbolt.org/z/nPxqWc - 但是现在,如果我传递相同的指针ab,并且*a = *b被转换为一个memcpy,这是未定义的行为,因为对于memcpy,“内存区域不能重叠。”(引用自手册)。那么编译器在使用memcpy时是错误的,还是我在编写此类赋值时出错了? - not-a-user

8
你是不是想说“复数”这个术语,它包含实部和虚部?如果不是,请给出一个例子,因为在C语言中,“complex”并没有具体的含义。当你直接复制一个结构体时,你会得到一个内存副本;但是否符合你的要求则取决于结构体的内容。例如,如果结构体包含指针,则两个副本都将指向相同的数据。这可能是你想要的,也可能不是,这取决于你的程序设计。要执行“智能”复制(或“深度”复制),你需要实现一个函数来执行复制操作。如果结构体本身包含指针和其他包含指针的结构体以及指向这些结构体的指针(也许这就是你所说的“复杂”),那么这可能非常难以实现,并且很难维护。简单的解决方法是使用C++,为每个结构体或类实现复制构造函数和赋值运算符,然后每个结构体或类负责自己的复制语义,你可以使用赋值语法,并且更容易维护。

3

是的,您可以使用简单的赋值语句将一个结构体实例分配给另一个结构体实例。

  • 对于非指针或不包含指针的结构体成员,赋值意味着复制。

  • 对于指针结构体成员,赋值意味着指针将指向另一个指针的相同地址。

让我们亲自来看看:

#include <stdio.h>

struct Test{
    int foo;
    char *bar;
};

int main(){
    struct Test t1;
    struct Test t2;
    t1.foo = 1;
    t1.bar = malloc(100 * sizeof(char));
    strcpy(t1.bar, "t1 bar value");
    t2.foo = 2;
    t2.bar = malloc(100 * sizeof(char));
    strcpy(t2.bar, "t2 bar value");
    printf("t2 foo and bar before copy: %d %s\n", t2.foo, t2.bar);
    t2 = t1;// <---- ASSIGNMENT
    printf("t2 foo and bar after copy: %d %s\n", t2.foo, t2.bar);
    //The following 3 lines of code demonstrate that foo is deep copied and bar is shallow copied
    strcpy(t1.bar, "t1 bar value changed");
    t1.foo = 3;
    printf("t2 foo and bar after t1 is altered: %d %s\n", t2.foo, t2.bar);
    return 0;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接