【问题标题】:How to return an array from a function in C?如何从C中的函数返回数组?
【发布时间】:2016-10-06 13:23:39
【问题描述】:

大多数网站都是这样说的:

C 编程不允许将整个数组作为参数返回 到一个函数。但是,您可以通过以下方式返回指向数组的指针 指定不带索引的数组名称。

我刚从指针开始,据我所知,指针变量是一个存储内存地址的变量。当我们使用 * 取消引用它时,我们会到达该内存地址并保存存储在那里的值。此外,在数组的情况下,指针必须指向第一个元素。

现在,如果我们的函数返回一个指向数组第一个元素的指针,如下例所示:

int * myFunction() {
   .
   .
   .
}
  1. 我们将如何取消引用它?
  2. “函数返回指针”是否意味着它正在返回指针指向的内存地址?

在这种情况下,

要记住的第二点是 C 不提倡返回 局部变量的地址到函数之外,所以你会 必须将局部变量定义为静态变量

  1. 究竟是什么静态变量? [我浏览了足够多的东西,但没有找到任何令人满意的东西]。维基百科定义:

在计算机编程中,静态变量是经过 静态分配,使其生命周期或“范围”延伸到 整个程序的运行。

另一个网站说,

静态存储类指示编译器保留一个本地 在程序的生命周期中存在的变量,而不是 每次进出时都创建和销毁它 范围。

请给我一个清晰的基本解释什么是静态变量以及它在这种情况下的相关性(从函数返回数组)。

我真的很困惑。

【问题讨论】:

  • 我会继续删除与您的问题无关的 C++ 标签。 C++ != C.
  • "... 将整个数组作为参数返回给函数" 将某些内容作为参数传递 与 returning 有根本的不同,所以请不要混淆术语。
  • 一次问题太多......
  • 我强烈怀疑许多网站会做出这样的声明。如果我在某处读到“返回 x to 一个函数”,我会关闭页面并阅读其他内容。准确的措辞太重要了,不能浪费时间写糟糕的教程或类似的东西
  • 在这个阶段可能太精细了,所以如果它令人困惑,请随意忽略它;指针必须指向数组的第一个元素是绝对没有限制的。拥有一个引用数组内部的指针是完全有效的并且通常非常有用。如果将数组传递给函数,则该参数会衰减为指向数组第一个元素的指针。

标签: c arrays pointers


【解决方案1】:

我们将如何取消引用它?

函数返回的指针变量。

通过使用适当的运算符*,一个例子:

int z = 5;
int* pointer_to_z = &z; // get memory address of z and store that in pointer_to_z
int can_i_have_my_z_back_please = *z; // check what value is at that memory address and store it, a copy is made here.

“返回指针的函数”是否意味着它正在返回 指针指向的内存地址?

它返回一个指针变量,这个变量保存着值的内存地址。基本上,“指向”一个值与“拥有它的地址”是一样的。

究竟什么是静态变量? [我浏览了足够多但没有找到 任何令人满意的东西]。

关于静态变量已经是什么,有很多很好的 SO 答案。总结一下(只考虑变量的生命周期,而不是它的链接)静态变量在初始化后对程序的其余部分有效,这意味着它的生命周期不像局部变量那样受范围限制:

void hello()
{
  int x = 5;
} // x destroyed here..

void hello_static()
{
  static int x = 5;
} // x will only be destroyed at the "end" of the program

这反过来意味着返回局部静态变量的指针(内存地址)是完全安全的,因为静态变量仍然可以访问:

int* return_my_static()
{
  static int a = 5;
  return &a;
}

int main()
{
  int* pointer_to_static = return_my_static(); // get the memory address of the static
  printf("%i", *pointer_to_static); // print out the value by dereferencing
}

但是,对局部非静态变量这样做会导致未定义的行为,因为指向的变量(它的内存地址)不再有效,因为它已被销毁:

int* return_local()
{
  int a = 5;
  return &a;
} // a is destroyed here.. oopsies

int main()
{
     int* pointer_to_local = return_local(); // get the memory address of the local.
     //local variable has been destroyed now and 'pointer_to_static' now points to garbage memory!
      printf("%i", *pointer_to_local); // Try to print out the value by dereferencing, this is undefined behaviour, anything can happen.
}

请注意,上面的代码可能会运行并输出预期的结果,但这完全是运气,这是未定义的行为,应该不惜一切代价避免,因为此时任何事情都可能发生。

【讨论】:

  • 谢谢吉尔,静态变量部分很清楚。 :) 我还有一个问题。在答案的第三个代码块中,在主函数内,'int* pointer_to_static = return_my_static(); printf("%i", pointer_to_static);'这里,return_my_static 将返回 &a,即存储在 pointer_to_static 中的内存地址。这使得 pointer_to_static 成为指针变量,对吧?那我们为什么要使用 int 呢?不是只在 printf 语句中才需要解引用吗?
  • 我说,我们为什么要用int star?我无法编辑评论。
  • @Narasimhan 没错,我这样做只是为了向您展示发生了什么。
【解决方案2】:

问题 3:

int a;

void foo() {
   int b
   static int c;
}

int main() {
    foo();
}

当程序启动时,a 和 c 的内存被分配并保持分配,直到程序退出。因此,在任何给定时间,都恰好在 a 和 c 上。每次任何人(这里是 main)调用 foo 时,都会在堆栈上分配 b,直到该函数返回为止。

关于问题3前面的报价:

返回a和c的地址是没有问题的,因为只要程序存在,它们就存在,但是返回一个地址给b是错误的,因为调用者一拿到手中的指针,指针就指向了无效的内存。

问题一:

您可以通过在前面放一个星号来取消引用指针。没关系,那个指针指向。它是一个数组,您可以递增或递减指针以到达您试图达到的索引,就像在一个简单的加法中一样:*(p + 4) 将访问第 5 个元素(因为 *(p + 0) 是第一个,所以 *(p + 1) 是第二个,依此类推)。

你也可以写成 p[4],而不是写 *(p + 4)。

所以假设你的函数看起来像这样:

int * myFunction() {
    static int array[8];
    return array;
}

然后return语句将返回数组的地址,它与数组的第一个元素的地址完全相同。

所以,有 int * p = myFunction();然后,您可以使用直观的语法 p[0] = 42; 访问数组。 ...; p[7] = 23;

问题 2:

返回指针的函数是返回指针的函数。指针是一个东西,它指向内存中的一个点。通常这称为内存地址,但 C 语言并不关心。所以,在实践中,“返回指针的函数”意味着它正在返回指针指向的内存地址,是的。

【讨论】:

    【解决方案3】:

    回到前面:

    什么是静态变量?

    具有 static 存储持续时间的对象在程序第一次启动时为其分配了存储空间,并且该存储空间一直保留到程序退出。如果满足以下条件,则对象具有static 存储持续时间:

    • 它的标识符是在文件范围内声明的(在任何函数的主体之外);或
    • 它的标识符是用static关键字声明的

    例子:

    #include <stdio.h>
    
    int g_var;                      // static storage duration
    
    void foo( void )
    {
      static int s_var = 10;        // static storage duration
      int l_var = 10;               // auto storage duration
    
      printf( "g_var = %d, s_var = %d, l_var = %d\n", g_var++, s_var++, l_var );
    }
    

    在这个sn-p中,g_vars_var都有static的存储持续时间; g_var 因为它是在文件范围内声明的,s_var 因为它是用 static 关键字声明的。凭借static 的存储持续时间,g_var 被隐式初始化为 0。请注意,s_var 在程序启动时被初始化一次 - 它不会被重新- 在后续调用foo 时初始化为10。因此,每次调用foo,输出将是

    g_var = 0, s_var = 10, l_var = 10
    g_var = 1, s_var = 11, l_var = 10
    g_var = 2, s_var = 12, l_var = 10
    ...
    

    l_var 具有 auto 存储期限 - 它的生命周期仅限于 foo 函数的生命周期。每次调用foo 时,l_varnew 实例的存储空间会在函数入口处分配和初始化,并在函数退出时释放。这很重要 - 当foo 退出时,l_var 不再存在,因此当foo 返回时,任何指向它的指针都将变为无效1.

    这就是为什么你不能做类似的事情

    int * bar( void )
    {
      int array[N];
      ...
      return array;
    }
    

    因为一旦bar 退出,array 不再存在,并且返回的指针是无效

    现在,解决此问题的一种方法是将数组声明为 static

    int * bar( void )
    {
      static int array[N];
      ...
      return array;
    }
    

    在这种情况下,array 在函数退出时并没有消失,所以指针仍然有效。

    不过……

    这会产生其他问题。只创建了一个array 的实例,它包含另一个调用bar 写入它的最后内容。代码不再是重入;它不能在执行中被安全地中断,然后在第一次调用完成之前被另一个函数调用。创建一个static 数组只是为了您可以干净地返回一个指向它的指针通常是错误的答案。

    要么将目标数组作为参数传递给函数:

    void foo( int *array, size_t arraySize )
    {
      ...
      array[i] = some_value;
      ...
    }
    

    动态地分配一个数组并返回指向它的指针:

    int * bar( void )
    {
      int *array = malloc( sizeof *array * N );
      if ( array )
      {
        // initialize array contents
      }
      return array;
    }
    

    这样做的问题是,当您完成时,其他人负责释放该内存。

    “函数返回指针”是否意味着它正在返回指针指向的内存地址?

    函数返回一个指针的,它是另一个对象的地址。在上面的代码中,bar 返回 表达式 array 的值,结果是array 的第一个元素的地址。

    在上述bar 的第二种情况下,返回的值等于&amp;array[0]

    我们将如何取消引用它?

    您可以通过两种方式取消引用指针 - 使用 * 取消引用运算符,或使用 [] 下标运算符。

    下标操作a[i]定义*(a + i) - 给定地址a,从a 偏移i 元素(不是字节)并取消引用结果。因此,您可以获取从bar 返回的指针并对其执行以下操作:

    int *p = bar();
    
    printf( "p[0] = %d\n", *p );
    printf( "p[0] = %d\n", *(p + 0) );
    printf( "p[0] = %d\n", p[0] );
    

    那么,这是否意味着数组和指针是一回事? 没有。数组不是指针;然而,在大多数情况下,数组表达式(即“N-element array of T”类型的表达式)将被转换(“decay”)为指针表达式(“pointer to T")。


    1. 很明显,l_var占用的内存位置还存在,所以指针的值不会突然变成垃圾什么的;但是,该内存位置现在可供其他东西使用,如果您尝试读取或写入该位置,则可能会导致问题。

    【讨论】:

      猜你喜欢
      • 2020-04-07
      • 2012-06-17
      • 2015-02-09
      • 2020-02-14
      • 2020-05-31
      • 2015-09-12
      相关资源
      最近更新 更多