为什么在我第一次遍历数组时返回本地数组指针有效?
Why does returning local array pointers work whenever I first iterate through the array?
我尝试 returning 数组指针以更好地理解它们的工作原理并发现了一些我无法解释的东西。
为什么当我在实际 return 数组之前遍历 return 数组时,下面的代码仍然有效(参见条件部分)?
是不是因为 C 是懒惰的,实际上并没有将数组写入内存,因为它认为它永远不会被访问?
#include <stdio.h>
//returns pointer to array terminated by (-1)
int* thisDoesntMakeAnySense(int randomNumber) {
int array[randomNumber + 1];
for(int i = 0; i < randomNumber; i++) {
array[i] = i;
}
array[randomNumber] = -1;
#ifdef ENABLE_CLUDGE
int i = 0;
while(array[i] != -1) {
printf("%d\n", array[i]);
i++;
}
#endif
return array;
}
int main() {
int* test = thisDoesntMakeAnySense(3);
while(*test != -1) {
printf("%d\n", *test);
test++;
}
return 0;
}
当您只是在函数中创建一个数组时,该数组的作用域就是该函数。
为了避免这种情况,您必须使用 malloc()
。 malloc()
允许您通过告诉操作系统动态分配内存 "I want this much memory. Give it to me and leave it alone, I'll do the rest"。
TLDR:
使用malloc()
确保数组在函数外持久存在。
Why does the following code work whenever I iterate through the return array before actually returning it? (uncomment section) Is it because C is lazy and does not actually write the array in memory as it thinks that it'll never get accessed anyways?
如果该程序确实如所述那样为您可靠地工作,那是因为您的 C 实现有特殊的怪癖,因为它们适用于所提供的特定代码并与之交互。
C 语言规范明确指出,当一个对象的生命周期结束时,任何指向它的指针(in)都变得不确定。尝试访问不确定指针值指向的对象会产生未定义的行为。在任何给定的 运行 上,未定义的行为绝对可以变成您描述为 "it works" 的东西,但 C 没有提供依赖它的理由。
是的,当您的编译器发现数组在返回之前未被读取时,它会消除写入数组的代码。
这不是懒惰;这是优化。编译器必须做 更多 工作来解决这些问题。在编译器中做更多工作的一个目标是创建一个程序,它在执行时做 less 工作(或使用更少 space 或其他资源)。
C 标准不保证此行为,但它是编译器的一个共同特征。
请注意,编译器能够忽略您在 main
中尝试的数组读取,因为这些读取未由 C 标准定义。在 thisDoesntMakeAnySense
中,数组 array
在函数开始时创建,并且(在 C 标准使用的计算模型中)在函数 returns 时销毁。虽然函数 returns 指向数组的指针,但数组不存在(在模型中),并且指针无效。因为指针无效,所以编译器不需要为 main
中使用指针的代码赋予任何意义。因此,C 标准允许编译器推断该程序写入数组但从不读取数组,甚至在 main
中也不读取,因此对数组的写入没有效果,可能会被删除。
一般来说,不需要编译器来生成完全按照编写的源代码执行您的程序的程序。 C 标准只要求编译器(或通常的 C 实现,包括所有标准头文件和库以及支持软件)生成与源代码具有相同可观察行为的程序。可观察到的行为包括:
- 数据写入文件。
- 输入和输出交互。
- 访问可变对象。
因此,printf
语句的输出是可观察到的行为。但是写入从未使用过的数组是不可观察的行为,因此不需要编译器为其生成代码。
关于恰好使您的程序看起来正常工作的代码,该代码从数组中读取并写入输出(这是可观察到的行为)。所以程序必须1 写入那个输出。您的编译器显然通过实际创建数组、写入数组、然后读取数组并将其写入标准输出来实现这一点。然后,在返回 main
时,由于数组已创建且其内存中的数据尚未更改,因此 main
中用于打印它的代码恰好“有效”。但是,编译器可以通过使用编译时生成的常量字符串简单地打印输出而不使用数组来实现所需的可观察行为。在这种情况下,main
中的代码将失败。
脚注
1 如果程序没问题,程序必须写入输出。然而,程序执行后期存在未定义行为的事实毒化了上游执行。如果程序控制进入的路径无条件地存在未定义的行为,则整个路径的行为都是未定义的。
我尝试 returning 数组指针以更好地理解它们的工作原理并发现了一些我无法解释的东西。
为什么当我在实际 return 数组之前遍历 return 数组时,下面的代码仍然有效(参见条件部分)?
是不是因为 C 是懒惰的,实际上并没有将数组写入内存,因为它认为它永远不会被访问?
#include <stdio.h>
//returns pointer to array terminated by (-1)
int* thisDoesntMakeAnySense(int randomNumber) {
int array[randomNumber + 1];
for(int i = 0; i < randomNumber; i++) {
array[i] = i;
}
array[randomNumber] = -1;
#ifdef ENABLE_CLUDGE
int i = 0;
while(array[i] != -1) {
printf("%d\n", array[i]);
i++;
}
#endif
return array;
}
int main() {
int* test = thisDoesntMakeAnySense(3);
while(*test != -1) {
printf("%d\n", *test);
test++;
}
return 0;
}
当您只是在函数中创建一个数组时,该数组的作用域就是该函数。
为了避免这种情况,您必须使用 malloc()
。 malloc()
允许您通过告诉操作系统动态分配内存 "I want this much memory. Give it to me and leave it alone, I'll do the rest"。
TLDR:
使用malloc()
确保数组在函数外持久存在。
Why does the following code work whenever I iterate through the return array before actually returning it? (uncomment section) Is it because C is lazy and does not actually write the array in memory as it thinks that it'll never get accessed anyways?
如果该程序确实如所述那样为您可靠地工作,那是因为您的 C 实现有特殊的怪癖,因为它们适用于所提供的特定代码并与之交互。
C 语言规范明确指出,当一个对象的生命周期结束时,任何指向它的指针(in)都变得不确定。尝试访问不确定指针值指向的对象会产生未定义的行为。在任何给定的 运行 上,未定义的行为绝对可以变成您描述为 "it works" 的东西,但 C 没有提供依赖它的理由。
是的,当您的编译器发现数组在返回之前未被读取时,它会消除写入数组的代码。
这不是懒惰;这是优化。编译器必须做 更多 工作来解决这些问题。在编译器中做更多工作的一个目标是创建一个程序,它在执行时做 less 工作(或使用更少 space 或其他资源)。
C 标准不保证此行为,但它是编译器的一个共同特征。
请注意,编译器能够忽略您在 main
中尝试的数组读取,因为这些读取未由 C 标准定义。在 thisDoesntMakeAnySense
中,数组 array
在函数开始时创建,并且(在 C 标准使用的计算模型中)在函数 returns 时销毁。虽然函数 returns 指向数组的指针,但数组不存在(在模型中),并且指针无效。因为指针无效,所以编译器不需要为 main
中使用指针的代码赋予任何意义。因此,C 标准允许编译器推断该程序写入数组但从不读取数组,甚至在 main
中也不读取,因此对数组的写入没有效果,可能会被删除。
一般来说,不需要编译器来生成完全按照编写的源代码执行您的程序的程序。 C 标准只要求编译器(或通常的 C 实现,包括所有标准头文件和库以及支持软件)生成与源代码具有相同可观察行为的程序。可观察到的行为包括:
- 数据写入文件。
- 输入和输出交互。
- 访问可变对象。
因此,printf
语句的输出是可观察到的行为。但是写入从未使用过的数组是不可观察的行为,因此不需要编译器为其生成代码。
关于恰好使您的程序看起来正常工作的代码,该代码从数组中读取并写入输出(这是可观察到的行为)。所以程序必须1 写入那个输出。您的编译器显然通过实际创建数组、写入数组、然后读取数组并将其写入标准输出来实现这一点。然后,在返回 main
时,由于数组已创建且其内存中的数据尚未更改,因此 main
中用于打印它的代码恰好“有效”。但是,编译器可以通过使用编译时生成的常量字符串简单地打印输出而不使用数组来实现所需的可观察行为。在这种情况下,main
中的代码将失败。
脚注
1 如果程序没问题,程序必须写入输出。然而,程序执行后期存在未定义行为的事实毒化了上游执行。如果程序控制进入的路径无条件地存在未定义的行为,则整个路径的行为都是未定义的。