【问题标题】:C pointer array scope and function callsC 指针数组范围和函数调用
【发布时间】:2010-11-26 17:29:26
【问题描述】:

我有这种情况:

{
    float foo[10];
    for (int i = 0; i < 10; i++) {
         foo[i] = 1.0f;
    }
    object.function1(foo); // stores the float pointer to a const void* member of object
}
object.function2(); // uses the stored void pointer

在第二个函数调用中浮点指针的内容是否未知?当我运行我的程序时,我似乎得到了奇怪的结果。但是,如果我将 float foo[10] 声明为 const 并在声明中对其进行初始化,我会得到正确的结果。为什么会这样?

【问题讨论】:

  • 这就是为什么我总是在同一列中排列相应的花括号。这样可以更轻松地检测范围内的变化。
  • 保留对局部变量的引用或指针总是会变成未定义的行为。运气好的话,立马就粉了,不浪费一天调试时间……
  • @Imagist:使用这种语法,} 与for 对齐,因此您仍然可以清楚地看到范围的开始和结束位置。我不明白这有什么不同。

标签: c++ arrays scope


【解决方案1】:

对于第一个问题,是的,一旦超出范围就使用 foo 是不正确的。我不确定它是否在规范中定义了行为,但这样做绝对是不正确的。最好的情况是您的程序将立即崩溃。

至于第二个问题,为什么让它 const 起作用?这是实现的产物。可能发生的情况是数据正在写入 DLL 的数据部分,因此在程序的整个生命周期内都是有效的。相反,原始样本将数据放在堆栈中,它的生命周期要短得多。代码仍然是错误的,它恰好可以工作。

【讨论】:

    【解决方案2】:

    是的,调用 function2 时 foo[] 超出范围。它是一个自动变量,存储在堆栈中。当代码退出它定义的块时,它被释放。您可能已经在别处存储了对它的引用(指针),但这没有意义。

    【讨论】:

      【解决方案3】:

      在这两种情况下,您都会得到未定义的行为。任何事情都有可能发生。

      您正在存储一个指向本地声明数组的指针,但是一旦退出包含数组定义的范围,该数组 - 并且其所有成员都被销毁。

      您存储的指针现在不再指向浮点数,甚至不再指向可用于浮点数的有效内存地址。它可能是一个被重用于其他东西的地址,或者它可能继续包含未更改的原始数据。无论哪种方式,尝试取消引用指针仍然无效,无论是读取还是写入浮点值。

      【讨论】:

        【解决方案4】:

        对于任何这样的声明:

        {
           type_1 variable_name_1;
           type_2 variable_name_2;
           type_3 variable_name_3;
        }
        

        声明,变量是在栈上分配的。

        你可以打印出每个变量的地址: printf("%p\n", variable_name ) 您会看到地址大致增加了少量(但并不总是完全等于),每个变量需要存储其数据的空间量。 当到达“}”并且变量超出范围时,堆栈变量使用的内存将被回收。只需从称为“堆栈指针”的特殊指针中减去一些数字即可有效地完成此操作,该指针表示新堆栈变量的数据将在何处分配数据。通过递增和递减堆栈指针,程序有一个非常快速解决变量内存的方法。每个主要处理器都为堆栈指针维护一块特殊的内存,这是非常重要的概念。

        您的数组的内存也从程序的数据堆栈中推送和弹出,您的数组指针是指向程序堆栈内存的指针。虽然语言规范说访问范围外变量拥有的数据具有未定义的后果,但结果通常很容易预测。 通常,您的数组指针将继续保存其原始数据,直到为新的堆栈变量分配并分配数据(即内存被重新用于其他目的)。

        所以不要这样做。复制数组。

        我不太清楚标准对常量数组的规定(可能是同一件事——当原始声明超出范围时,内存无效)。但是,您的不同行为是可以解释的如果您的编译器为在程序启动时初始化的常量分配了一块内存,稍后,当它进入范围时,foo 会指向该数据。至少,如果 我 正在编写一个编译器,那可能就是我会做的,因为它既非常快又会使用最少的内存。这个理论很容易通过以下方式检验:

        void f()
        {
           const float foo[2] = {99, 101};
           fprintf( "-- %f\n", foo[0] );
           const_cast<foo*>(foo)[0]  = 666; 
        } 
        

        调用 foo() 两次。如果在调用之间打印的值发生了变化(或者抛出了无效的内存访问异常),那么可以肯定的是 foo 的数据被分配在上面代码覆盖的常量的特殊区域中。

        在特殊区域分配内存不适用于非常量数据,因为递归函数可能会导致堆栈上同时存在许多单独的变量副本,每个副本可能保存不同的数据。

        【讨论】:

          【解决方案5】:

          在这两种情况下都是未定义的行为。您应该考虑在控制离开块时释放基于堆栈的变量。

          【讨论】:

          • 第一个函数调用中是否也未定义?
          • (object.function1(foo) 是正确的)在这两种情况下,我的意思是它是否是 const。它可能会工作或崩溃或炸毁您的整个计算机。不要依赖于它对你的实现起作用的事实
          【解决方案6】:

          目前正在发生的事情可能只是设置了一个指针(看不到代码,所以我不能确定)。该指针将指向对象foo,此时该对象在范围内。但是当它超出范围时,所有的地狱都会崩溃,并且 C 标准不能保证一旦超出范围,该数据会发生什么。它可以被任何东西覆盖。它适用于 const 数组,因为你很幸运。不要那样做。

          如果您希望代码按原样正常工作,function1() 将需要将数据复制到对象成员中。这意味着您还必须知道数组的长度,这意味着您必须将其传入或使用一些不错的终止方法。

          【讨论】:

            【解决方案7】:

            与 foo 关联的内存超出范围并被回收。 在 {} 之外,指针无效。

            让对象管理自己的内存而不是引用外部指针是个好主意。在这种特定情况下,您的对象可以在内部分配自己的 foo 并将数据复制到其中。但是,这实际上取决于您要实现的目标。

            【讨论】:

              【解决方案8】:

              对于像这样的简单问题,最好给出一个简单的答案,而不是关于堆栈和内存地址的 3 段。

              有两对大括号{},一对在另一对里面。该数组是在第一个左大括号 { 之后声明的,因此它在最后一个大括号之前停止存在 }

              结束

              在回答问题时,无论您自己对问题的理解程度如何,您都必须以提问者的水平回答问题,否则您可能会使学生感到困惑。

              -经验丰富的ESL老师

              【讨论】:

                猜你喜欢
                • 2014-05-31
                • 2021-11-25
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2015-02-19
                • 1970-01-01
                相关资源
                最近更新 更多