为什么在C语言中不能返回固定大小/常量数组?

15
我想知道为什么在C语言中不能返回数组?因为数组只是由大小信息支持的指针(以使sizeof工作)。起初我认为这样做是为了防止我返回在栈上定义的数组,但是没有任何东西可以阻止我返回指向我的栈上某些内容的指针(gcc会警告我,但代码编译)。我还可以返回静态存储的字符数组。顺便说一下,在Linux上,它存储在.rodata中,const数组也存储在那里(使用objdump检查),所以我可以返回数组(将其转换为指针)并且它可以工作,但据我所知,这只是特定于实现的(其他操作系统/编译器可能将const存储在堆栈上)。
我有两个想法如何实现返回数组:只需将其作为值复制(与结构体相同)。我甚至可以将数组包装到结构中返回!),并自动创建指向它的指针或允许用户返回const数组,并创建这样的约定,即此类数组应具有静态存储期(与字符串相同)。这两种想法都很简单! 那么,我的问题是,为什么K&R没有实现类似的功能?

1
当然,从技术上讲,可以使数组像其他内置类型一样可复制和可分配,而它们不是这样做有点烦人。我经常问自己这个问题。 - juanchopanza
3
如果我理解正确,OP想知道为什么会这样。设计决策有其原因并非罕见。 - juanchopanza
1
因为这就是堆栈变量的工作原理。当您在堆栈上声明变量时,只需增加您的堆栈指针。当您的函数(或范围,就此而言)完成时,您的堆栈指针将被递减回来,您基本上失去了局部变量(在您的情况下是数组)。 - Sam Protsenko
4
天啊,无法返回一个整数或者一个包含数组的结构体吗?这是一种“设计决策”。从技术角度来说是完全可行的。 - juanchopanza
2
@SamProtsenko 在C标准中,没有提到使用堆栈来传递和返回函数参数和返回值。(尽管大多数实现确实使用堆栈) - wildplasser
显示剩余14条评论
4个回答

18

从技术上讲,你可以返回一个数组;你只是不能直接这样做,而必须将其包装在结构体中:

struct foo {
    int array[5];
};

struct foo returns_array(void) {
    return((struct foo) {
        .array = {2, 4, 6, 8, 10}
    });
}

尽管C语言具备这种能力,为什么它不直接允许你这样做仍然是一个好问题。可能与它不支持整个数组赋值有关:

void bar(int input[5]) {
    int temp[5];

    temp = input;   <-- Doesn't compile
}

尽管如此,更奇怪的是,通过参数传递进行整个数组复制是被支持的。如果有人知道如何找到 ANSI 委员会对此事的决定,那将是有趣的阅读。
然而,
引用:
“毕竟,数组只是由大小信息支持的指针(以使 sizeof 正常工作)。”
这是不正确的。数组没有显式指针,也没有存储大小。数组被存储为原始值,紧密地打包在一起;大小仅在编译器内部得知,并且从未作为运行时数据在程序中显式表示。当您尝试将其用作指针时,数组会“退化为指针”。

是的,这就是我在第一条评论中所说的。 - wildplasser
2
仅当数组是结构体中的一个字段时,才支持通过参数传递进行整个数组复制。不支持直接进行整个数组复制。 - mcleod_ideafix

14

数组不是“由大小信息支持的指针”。

数组是一块相同类型元素的连续内存块。其中没有指针。

由于数组是一个对象,因此可以形成一个指向数组或其元素之一的指针。但这样的指针不是数组的一部分,也没有与数组一起存储。这样说“int只是由大小为1的int支持的指针”是毫无意义的。

编译器知道数组的大小的方法与任何对象的大小信息的获得方式相同。例如double d;则已知sizeof d等于sizeof(double),因为编译器记得d是一个double类型的对象。

没有阻止我从返回指向栈上的某些东西的指针。

C标准禁止您这样做(并使用返回的指针)。如果编写违反标准的代码,则自行承担风险。

而且我也可以返回字符串字面值。

字符串字面值是char数组。当您在return语句中使用数组时,它会被转换为指向第一个元素的指针。

为了使数组可以通过值进行返回(和分配),必须更改关于数组转换为指针的规则(有时称为“衰减”)。这是可能的,但是K&R在设计C时决定使衰减几乎普遍存在。

事实上,可以有一种类似C的语言,但根本没有衰减。也许事后看来,这将节省很多混乱。但是他们选择按照目前的方式实现C。


在K&R C中,也无法通过值返回结构。除了基本类型的任何复制操作都必须使用memcpy或等效的迭代复制。考虑到1970年代硬件资源的状况,这似乎是一个合理的设计决策。

ANSI C 引入了通过值返回结构体的可能性,然而即使他们想改变衰减规则,也已经为时过晚;这将破坏很多依赖于衰减规则的现有代码。


2
“原因”是数组在大多数表达式中会“衰减”为指针,如果允许数组的赋值,那么将会出现错误。如果从函数中返回一个数组,您将无法将其与普通指针区分开来。例如,如果f()返回double[5],则初始化过程将无法进行区分。
double* A=f();

这将是有效的。 A 将取一个临时对象的地址,在C语言中,该对象只存在于调用 f 的完整表达式结束之前。因此,A 将成为悬挂指针,即指向不再有效的地址的指针。

总结一下:最初决定让数组在大多数情况下类似于指针,这就导致了数组不能被分配或由函数返回。


2
因为如果突然有一种语言修订允许函数能够返回完整的数组,那么这个修订也应该处理以下情况:
  • 允许数组之间赋值(因为如果一个函数返回一个数组,那是因为它将被分配给调用者函数中的一个数组变量)
  • 允许将完整数组作为值参数传递(因为数组的名称不再是指向其第一个元素的指针,否则会与第一种情况冲突)

如果允许这些结构,那么现有程序将无法通过将数组的名称作为参数传递给函数,并期望函数修改该数组来工作。

此外,使用数组名称作为指针将其分配给指针变量的现有程序也将无法工作。

因此,虽然从技术上讲可行,但使数组作为完整实体可以分配、返回等操作将破坏很多现有程序。

请注意,结构体可以“升级”,因为K&R C中没有先前的语义将变量结构的名称与指向自身的指针相关联。任何必须使用结构作为参数或返回值的函数都必须使用指向它们的指针。


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