【问题标题】:Why does returning local array pointers work whenever I first iterate through the array?为什么当我第一次遍历数组时返回本地数组指针起作用?
【发布时间】:2019-12-16 08:02:05
【问题描述】:

我尝试了返回数组指针以更好地了解它们的工作原理,并发现了一些我无法解释的东西。

为什么当我在实际返回之前遍历返回数组时,以下代码会起作用(请参阅条件部分)?
是不是因为 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;
}

【问题讨论】:

  • 是不是因为 C 是惰性的,实际上并没有将数组写入内存 不是,是因为数组有本地作用域,当你退出函数时,他的生命周期结束,到使其可见:1) 使用动态内存 int *array = malloc((randomNumber + 1) * sizeof *array); 或 2) 使其成为 static : static int array[randomNumber + 1];
  • "为什么下面的代码可以工作" --> 你期望什么行为?

标签: c pointers undefined-behavior local-variables


【解决方案1】:

当您只是在函数中创建一个数组时,该数组的作用域就是该函数。

为了避免这种情况,您必须使用malloc()。 malloc() 让您通过告诉操作系统“我想要这么多内存。把它给我,别管它,剩下的事情我来做”来动态分配内存。

TLDR:

使用malloc() 来确保数组在函数之外持续存在。

【讨论】:

  • 好吧,有点夸张。它衰减为指针。它是通过指针算法访问的。这是一个花哨的指针。
  • int 与 double 一样多。实际上甚至更少。
  • @MichaelBianconi 不,不是。真的不是。一旦学习 C 的人停止认为数组是花哨的指针就更好了。
  • 指针是一个对象,它的值是另一个对象的地址。数组是一个对象,其值是其他对象的连续容器。数组不是花哨的指针。
  • 不!数组不是指针!!让我再说一遍:数组不是指针。一个数组似乎在某些结构中表现得像一个指针,但这仅仅是因为“数组衰减为一个指针”。但是数组(变量,无论你想怎么称呼它的对象)与指针根本不同。像指针一样思考数组最终会回来咬你:例如stackoverflow.com/questions/21972465/…
【解决方案2】:

为什么当我在实际返回之前遍历返回数组时,以下代码会起作用? (取消注释部分)是不是因为 C 是惰性的,并且实际上并没有将数组写入内存,因为它认为它永远不会被访问?

如果程序确实如所描述的那样为您可靠地工作,那么这是因为您的 C 实现的特定怪癖,因为它们适用于呈现的特定代码并与之交互。

C 语言规范很清楚,当对象的生命周期结束时,指向它的任何指针(入)都变得不确定。尝试访问由不确定指针值指向的对象会产生未定义的行为。在任何给定的运行中,未定义的行为绝对会变成您描述为“它有效”的东西,但 C 没有提供依赖它的理由。

【讨论】:

    【解决方案3】:

    是的,当你的编译器发现数组在返回之前没有被读取时,它会消除写入数组的代码。

    这不是懒惰;这是优化。编译器必须做 更多 工作来解决这些问题。在编译器中做更多工作的目标是创建一个在执行时做更少工作(或使用更少空间或其他资源)的程序。

    这种行为不受 C 标准的保证,但它是编译器的常见特性。

    请注意,编译器能够忽略您在main 中尝试的数组读取,因为这些读取不是由 C 标准定义的。在thisDoesntMakeAnySense 中,数组array 在函数开始时创建,并且(在C 标准使用的计算模型中)在函数返回时销毁。尽管该函数返回一个指向数组的指针,但该数组不存在(在模型中),并且该指针无效。因为指针无效,所以编译器不需要为main 中使用该指针的代码赋予任何意义。因此,C 标准允许编译器推断该程序写入数组但从不读取数组,即使在 main 中也不行,因此对数组的写入无效并且可能会被删除。

    一般来说,编译器不需要编译器来生成一个完全按照源代码编写的程序来执行的程序。 C 标准只需要编译器(或通常的 C 实现,包括所有标准头文件和库以及支持软件)来生成与源代码具有相同可观察行为的程序。可观察的行为包括:

    • 数据写入文件。
    • 输入和输出交互。
    • 对易失性对象的访问。

    因此,printf 语句的输出是可观察的行为。但是写入一个从未使用过的数组是不可观察的行为,所以编译器不需要为它生成代码。

    关于恰好使您的程序看起来可以工作的代码,该代码从数组中读取并写入输出(这是可观察到的行为)。因此程序必须1 写入该输出。您的编译器显然通过实际创建数组、写入数组、然后读取数组并将其写入标准输出来实现这一点。然后,在返回main 时,由于数组已创建并且其内存中的数据尚未更改,main 中用于打印它的代码恰好“工作”。但是,编译器可以通过简单地使用编译时生成的常量字符串而不使用数组来打印输出来实现所需的可观察行为。在这种情况下,main 中的代码会失败。

    脚注

    1 如果程序正常,则程序必须写入输出。然而,在程序执行后期存在未定义行为这一事实会毒害上游执行。如果程序控制进入一个无条件地存在未定义行为的路径,则整个路径的行为是未定义的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-01-22
      • 2014-12-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-30
      • 2012-11-06
      相关资源
      最近更新 更多