【问题标题】:return local value(array) by address按地址返回本地值(数组)
【发布时间】:2021-09-29 21:44:11
【问题描述】:

我有一个关于按地址从函数返回局部变量的问题。 我知道每个局部变量都有一个生命周期。 但是当我返回一个数组并直接打印例如第一个和第二个单元格时,我仍然得到值。 但是当我只添加一行时,它已经被破坏了。 问题是为什么一旦函数关闭就不会发生这种情况。

int* f1();
int main(){
   int *p=f1(); 
   printf("%d %d\n",p[0],p[1]);
   return 0;
}
int* f1(){  
   int arr[4]={1,2,3,4};    
   return arr;
}

输出:1 2

int* f1();
int main(){
   int *p=f1();
   printf("hey\n"); // **add this line**
   printf("%d %d\n",p[0],p[1]);
   return 0;
}
int* f1(){  
   int arr[4]={1,2,3,4};    
   return arr;
}

输出:1 11788742452

【问题讨论】:

  • 您不能返回指向局部变量的指针。一旦函数完成,局部变量就不再存在。你看到的是未定义的行为。您已经看到了可以随时覆盖的剩余部分。在第二个程序中你可以看到添加的 printf 已经覆盖了剩余的
  • 正如您所写,该数组不再有效。你很不幸得到了你想要的结果。 Sh*t 发生了。未定义的行为没有任何保证:结果可能是对的,可能是错的,也可能是天崩地裂。通常,如果内存没有被覆盖的原因,您会得到想要的结果,这可能是任意数量的事情。
  • 我知道我不能返回一个局部变量的指针,问题是为什么当我返回并直接打印第一个和第二个单元格时我仍然得到正常值,但是当我添加一行示例打印然后打印第一个和第二个单元格的值我得到垃圾值。 @Jabberwocky
  • 在一种情况下内存被重用,在另一种情况下(还没有)。旁注:如果你闯红灯,你并不总是被卡车压扁。
  • @רוניג'קויטולי 刷新浏览器并阅读我更新的评论

标签: arrays c memory return undefined-behavior


【解决方案1】:

函数f1中声明的数组arr

int* f1(){  
   int arr[4]={1,2,3,4};    
   return arr;
}

具有自动存储期限。这意味着退出该功能后它将不再存在。因此从函数指针返回的值将是无效的,并且在 main 中取消引用指针会调用未定义的行为。

如果数组的第一个元素具有静态存储持续时间,您可以返回一个指针,例如

int* f1(){  
   static int arr[4]={1,2,3,4};    
   return arr;
}

另一种方法是在函数内动态分配数组

#include <string.h>
#include <stdlib.h>
#include <stdio.h>

//...

int* f1(){
    int *arr = malloc( sizeof( int[4] ) );  

    if ( arr != NULL )
    {
        memcpy( arr, ( int[] ) { 1, 2, 3, 4 }, sizeof( int[4] ) );
    }
   
    return arr;
}

在这种情况下,您将需要释放 main 中分配的数组,因为它不再被用作

int *p=f1();
//...
free( p );

【讨论】:

  • @chux-ReinstateMonica 谢谢。这是一个错字。:)
  • 另一种方法是返回一个包含数组的结构。
【解决方案2】:

如果你用汇编语言编写类似的代码会发生以下情况:

  • main 致电f1
  • f1 在堆栈上保留一些空间(通过调整堆栈指针)并将 1、2、3 和 4 放在那里。
  • f1 将该空间的地址放入用于返回值的寄存器中。
  • f1 弹出堆栈(通过相反的方式调整堆栈指针),这意味着不再保留内存。
  • f1 返回main
  • main 使用返回值从内存中加载数据。由于没有任何东西改变内存,所以这是可行的。
  • main 准备调用 printf 并将其加载的数据传递给它。
  • 调用printf 并执行printf 使用堆栈。这会更改那里的数据。
  • printf 返回main
  • main 在调用printf 后使用较早的返回值从内存中加载数据时,堆栈区域中的内存已更改。所以负载会获取新的更改数据,而不是来自f1 的旧数据。

当您为此代码使用 C 时,编译器会遵循 C 标准中抽象指定的规则。如果它只是机械地生成上述汇编代码,您会看到上述行为。但是,C 标准在 f1 中定义了数组的抽象 生命周期,并表示生命周期在函数返回时结束。它进一步说,当指针指向的对象的生命周期结束时,指针的值变得不确定。当编译器编译您的代码时,它会寻求优化它生成的代码。它不是机械地生成汇编,而是试图找到执行程序定义行为的“最佳”代码。如果您使用具有不确定值的指针或尝试访问生命周期已结束的对象,则程序没有定义的行为,因此编译器的优化可能会产生新程序员意想不到的结果。

【讨论】:

    【解决方案3】:

    Clearing内存实际上意味着程序不再控制那部分内存。它现在由操作系统支配。操作系统可以使用该内存做任何事情。
    所以在这种情况下,

    int* f1();
    int main(){
       int *p=f1(); 
       printf("%d %d\n",p[0],p[1]);
       return 0;
    }
    int* f1(){  
       int arr[4]={1,2,3,4};    
       return arr;
    }
    

    f1()函数执行结束后,操作系统取回对arr内存的控制权。它可以将该内存分配给其他程序。 不幸的是,在那段时间里,操作系统没有在那个内存中分配任何任务。这就是它没有被覆盖的原因,您可以获得该内存中的信息。
    内存被使用、释放然后被覆盖。

    在第二种情况下,操作系统使用该内存来运行printf() 语句或其他一些东西。所以那段记忆是overwritten
    正式名称为:memory use and release

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-08-16
      • 1970-01-01
      • 2017-09-21
      • 1970-01-01
      • 2012-03-13
      • 1970-01-01
      • 1970-01-01
      • 2011-06-29
      相关资源
      最近更新 更多