结构体有什么特别之处?

83

我知道在C语言中我们不能从函数中返回数组,只能返回指向数组的指针。但我想知道结构体的特殊性质是什么,使它们可以从函数中被返回,即使它们可能包含数组。

为什么使用结构体封装后,下面的程序是有效的?

#include <stdio.h>

struct data {
    char buf[256];
};

struct data Foo(const char *buf);

int main(void)
{
    struct data obj;
    obj = Foo("This is a sentence.");
    printf("%s\n", obj.buf);
    return 0;
}

struct data Foo(const char *buf)
{
    struct data X;
    strcpy(X.buf, buf);
    return X;
}

1
你可以使用“联合”来完成相同的事情。联合有什么特殊之处? - user253751
21
你应该问为什么在C语言中数组如此奇怪。 - CodesInChaos
当返回一个结构体时,如果该结构体无法适应几个寄存器,则编译器会分配一个“隐藏”的内存。然后通过memcpy()将该结构体复制到隐藏内存中,再次通过memcpy()将其复制到调用者的结构体变量中。那个“隐藏”的内存对于所有其他函数来说都是不可见的。两次额外的memcpy()调用和“隐藏”内存的丢失是结构体不应该从函数中“传递”或“返回”的主要原因。最好的方法是将指向结构体的指针传递给函数。 - user3629249
三个答案都没有涉及结构体的传递,它们只讨论了数组的传递,但并没有回答问题。 - user3629249
1
@user3629249 - 无法回答这个问题,因为这个问题的前提是缺乏理解。唯一回答这个问题的方法是尝试解释为什么这个问题不能被问。想象一下,如果我问你“为什么蓝色和红色是相同的颜色?”你会很快解释为什么你无法回答这个问题。 - Hogan
显示剩余2条评论
4个回答

102

同样的问题更好的表达方式是“数组有什么特别之处”,因为数组具有特殊的处理方式,而不是struct

将数组通过指针传递和返回的行为可以追溯到C语言最初的实现。数组会“衰变”成指针,这会导致很多混淆,尤其是对于刚接触该语言的人来说。相反地,结构体的行为就像内置类型(如intdouble等)。这包括嵌入在struct中的任何数组,除了灵活数组成员,它们不会被复制。


4
“导致相当混乱”,确实。“x”和“&x”具有相同的值/地址,这有点不可思议。新手弄错间接引用也就不足为奇了:( - Martin James
5
我认为,在语言中首次添加结构体时,最初存在一些限制,不能将它们传递给函数或从函数返回,但这些限制很快被明确标记为编译器的缺陷,并得到了纠正,这并不意味着想要传递和返回结构体有任何问题。 - Steve Summit
1
@Steve Summit:即使可能(根据K&R的说法不可能),为什么要传递/返回结构体而不是指向它们的指针?除非您从未弄清楚如何使用指针,否则为什么要使用C而不是例如Java? - jamesqf
9
你真的想使用指针来移动它们吗?这样做肯定不会节省空间。 @jamesqf:struct Point {short x,y,z;}; - Mark VY
7
@jamesqf 我不确定这是否值得回应。如果你认为C语言只不过是汇编语言的高级形式,如果你认为永远没有理由不使用指针,我或许能理解你认为传递结构体是毫无意义的想法。但对于我们大多数人而言,我们将C语言视为高级语言(尽管它在高级语言中属于低级别的一种),我们将C语言的类型系统视为通用的(除了数组作为次等公民的例外),那么我们为什么不想要传递或返回结构体呢? (顺便说一句,如果我没记错的话,K&R1曾经表示结构体传递正在进行中,在该书出版时,在V7 cc中可以使用。) - Steve Summit
显示剩余3条评论

38
首先,引用《C11》,章节§6.8.6.4,return语句,(我强调):
如果执行带有表达式的return语句,则返回表达式的值作为函数调用表达式的值返回给调用者。
返回结构体变量是可行的(也是正确的),因为返回的是结构体的值。这与返回任何原始数据类型(例如返回int)类似。
另一方面,如果使用“return ”返回一个数组,它实际上返回数组的第一个元素的地址注意,如果该数组是局部于被调用函数的,则在调用者中该地址会无效。因此,不能以这种方式返回数组。
因此,简而言之,struct没有什么特别之处,特殊性在于数组。
注:
再次引用《C11》,章节§6.3.2.1,(我强调):
除非它是sizeof运算符、_Alignof运算符或一元&运算符的操作数,或者是用于初始化数组的字符串字面值,否则具有类型“type的数组”的表达式将转换为类型“指向类型的指针”的表达式,该指针指向数组对象的初始元素,并且不是lvalue。[...]

OTOH 到底是什么?! - user5619103
1
@Sukl 这是“另一方面”的缩写 :) - Sourav Ghosh
1
@Sukl 我认为这些缩写大约和互联网本身一样古老。它们在Usenet辉煌时期被广泛使用,现在仍然存在于大多数论坛中。值得庆幸的是,即使对于不知道的人,谷歌也可以解码它们;-) 而且今天只有少数几个是经常使用的(最常见的)。 - chi
@chi 是的,Google非常聪明,能够解码它们。 - user5619103
1
@Sukl:你可能会发现AcronymFinder对于查找缩写词很有用。 - Jonathan Leffler

11

struct类型并没有什么特别的地方; 问题在于数组类型有一些特殊之处,这使得它们不能直接从函数中返回。

struct表达式被处理为任何其他非数组类型的表达式;它会计算出struct。因此,您可以做像以下这样的事情:

struct foo { ... };

struct foo func( void )
{
  struct foo someFoo;
  ...
  return someFoo;
}

表达式someFoo的求值结果是struct foo对象的;无论对象的内容是否包含数组,都会从函数中返回对象的内容。

数组表达式的处理方式不同;如果它不是sizeof或一元&运算符的操作数,或者它不是用于初始化声明中的另一个数组的字符串字面量,则该表达式会从类型"array of T"转换为"type to T"的指针,并且该表达式的值是第一个元素的地址。

因此,你不能通过值来从函数中返回一个数组,因为对任何数组表达式的引用都会自动转换为指针值。


-5

结构体默认情况下具有公共数据成员,因此在结构体的情况下可以在主函数中访问数据,但在类的情况下则不行。因此,结构体包装是有效的。


2
你有没有看到这个问题是关于C语言的?在C语言中,没有public/private的区别,也没有class。这个问题是关于为什么在C语言中需要使用struct来返回数组的值。 - PJTraill

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