结构字段中的数组是否始终持续存在 return?

Do arrays in struct fields always persist upon return?

我知道我不能 return 函数中的本地声明数组,因为数组存在于函数的堆栈中 space 并且在 returning 之后,指针将悬垂。

这个无效

int* test() {
    int x[3] = {1,2,3};
    return x;
}

main() {
    int* x = test();
}

因此,我对returning 包含 数组的结构感到紧张;因为它们只是结构的连续内存的一部分 space,所以 复制到 return.

即这完全没问题

typedef struct Container {
    int arr[3][3];
} Container;
Container getContainer() {
    Container c = {.arr = {{1,2,3}, {4,5,6}, {7,8,9}} };
    return c;
}

int main() {
    Container c = getContainer();
    // c.arr is "deep copied" to main's stack space
}

不过,我有一种强烈的直觉,而不是做类似的事情

void populateContainer(Container* c) {
    int arr[3][3] = {{1,2,3}, {4,5,6}, {7,8,9}};
    memcpy(c->arr, arr, 3*3 * sizeof(arr));
}

int main() {
    Container c;
    populateContainer(&c);
}

所以我的问题是,我是否应该只相信在 returned 结构时数组将始终按值安全地复制,并避免后一种模式?我总是使用 C99 标准。是否有编译器不尊重这一点,为此我应该使用更丑陋但看似更安全的地址传递模式?

考虑一个基本示例:

void populateInt(int *a){
    int x = 5;
    *a = x;
}

int main(void){
    int var;
    populateInt(&var);

    return 0;
}

函数populateInt中变量x的生命将在函数return中结束一次。 main 中的 var 会怎样?是否有人居住?

答案是 var 将在 populateInt 函数中填充 x 的值。您的代码的最后一个示例也是如此。

这也有效

void populateContainer(int a[][3]) {
    int arr[3][3] = {{1,2,3}, {4,5,6}, {7,8,9}};
    memcpy(a, arr, 3*3 * sizeof(int));
}

int main(void) {
    int arr[3][3];
    populateContainer(arr);

    return 0;
}

你不能从函数中 return 数组的原因是在大多数情况下,数组被转换为指向第一个元素的指针,所以你真正做的是 returning 指向局部变量的指针 undefined behavior.

当您 return 来自函数的结构时,不会发生此类转换。整个结构 return 按值编辑,包括它可能包含的任何数组。

这样做:

Container getContainer() {
    Container c = {.arr = {{1,2,3}, {4,5,6}, {7,8,9}} };
    return c;
}

int main() {
    Container c = getContainer();
    // c.arr is "deep copied" to main's stack space
}

绝对安全。

So my question is, should I just trust that the array will always be safely copied-by-value when the struct is returned, and avoid the latter pattern?

绝对是,对于任何过去、现在和可预见的未来一代的 C 标准。

Are there compilers which wouldn't respect this, and for which I should use the uglier-but-seemingly-safer address-passing pattern?

根据定义,没有 C 编译器会这样做(如果编译器会这样做,那么它就不是 C 编译器)。

下面是更长的解释。

您不能分配一个数组或从另一个数组初始化一个数组,或者return一个数组或将一个数组传递给函数。 None 之所以有效,是因为在大多数情况下数组 decay to pointers。 (当您将数组传递给函数时,实际上是将指针传递给它的第一个元素)。

但是你可以用任何类型的非数组数据完美地完成这些事情中的任何一个,即使它里面有数组。数据结构中的数组不会衰减,指向数组的指针也不会衰减。他们根本没有明智的方法来这样做。

根据6.9.1p3

The return type of a function shall be void or an object type other than array type.

关于对象类型的定义,来自6.2.5p1:

The meaning of a value stored in an object or returned by a function is determined by the type of the expression used to access it. (An identifier declared to be an object is the simplest such expression; the type is specified in the declaration of the identifier.) Types are partitioned into object types (types that fully describe objects), function types (types that describe functions), and incomplete types (types that describe objects but lack information needed to determine their sizes).

另请参阅 this question 关于对象类型。


总之,由于这是一个有效的对象类型:

struct Container {
    int arr[3][3];
};

如果您的编译器符合标准,您可以期望 Container c = getContainer(); 即使在函数堆栈内创建 Container 然后返回时也能正常工作,因为标准没有指定返回值的来源。