错误:函数返回局部变量的地址

56

我是一名C语言初学者,正在自学中。我正在编写以下函数:

char *foo(int x){
     if(x < 0){
        char a[1000];
        char b = "blah";
        x = x - 1;
        char *c = foo(x);
        strcpy(a, b);
        strcat(a, c);
        return a;
      }
    blah ...
}

我基本上想要返回一个已附加的字符串,但是我遇到了以下错误:

"error: function returns address of local variable",有什么建议如何解决这个问题吗?


可能是重复的C警告:函数返回局部变量的地址 - netcoder
2
当你编写问题时,它会根据问题内容建议一些可能的重复问题。你应该先检查一下这些问题。 - netcoder
我认为这可能会有所帮助。 - Luiz Cajueiro
char b = "blah"; 不是有效的 C 声明。另外,对于声明为 charbstrcpy(a, b) 也无效。你发布的是真实代码吗? - AnT stands with Russia
这个回答解决了你的问题吗?C警告:函数返回局部变量的地址 - Leos313
在过去的十年中,完全相同的三个链接已经被发布了。现在轮到谁来发布第四个链接回答同样的问题呢? - Richard Steinbrecht
8个回答

84
局部变量的生命周期仅在定义它的块内有效。当控制流程超出局部变量所在的块时,变量的存储空间不再被分配(不保证)。因此,在变量的生命周期区域外使用变量的内存地址将导致未定义的行为。
另一方面,您可以执行以下操作。
 char *str_to_ret = malloc (sizeof (char) * required_size);
  .
  .
  .
 return str_to_ret;

请使用 str_to_ret。在返回 str_to_ret 时,将返回由 malloc 分配的地址。 malloc 分配的内存来自堆,其生命周期跨越整个程序的执行。因此,在程序运行过程中的任何块和任何时间都可以访问内存位置。

还要注意,一旦你处理完分配的内存块,请使用 free 来避免内存泄漏。一旦释放了内存,就无法再次访问该块。


2
注释:封装/生命周期/责任设计中的缺陷:调用者初始化了一个Malloc,但被调用者必须释放它。此外,如果您不对传递给函数的值进行消毒/检查,可能会在堆中轻松地分配一个非常大的块... - Gewure
只是一个例子,用来解释对象的生命周期。 - phoxis

21

我想分享一个简单明了的(希望如此)代码示例,它应该可以解释自己!

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

/* function header definitions */
char* getString();                     //<- with malloc (good practice)
char * getStringNoMalloc();  //<- without malloc (fails! don't do this!)
void getStringCallByRef(char* reference); //<- callbyref (good practice)

/* the main */
int main(int argc, char*argv[]) {

    //######### calling with malloc
    char * a = getString();
    printf("MALLOC ### a = %s \n", a); 
    free(a);

    //######### calling without malloc
    char * b = getStringNoMalloc();
    printf("NO MALLOC ### b = %s \n", b); //this doesnt work, question to yourself: WHY?
    //HINT: the warning says that a local reference is returned. ??!
    //NO free here!

    //######### call-by-reference
    char c[100];
    getStringCallByRef(c);
    printf("CALLBYREF ### c = %s \n", c);

    return 0;
}

//WITH malloc
char* getString() {

    char * string;
    string = malloc(sizeof(char)*100);

    strcat(string, "bla");
    strcat(string, "/");
    strcat(string, "blub");

    printf("string : '%s'\n", string);

    return string;
}

//WITHOUT malloc (watch how it does not work this time)
char* getStringNoMalloc() {

     char string[100] = {};

     strcat(string, "bla");
     strcat(string, "/");
     strcat(string, "blub");
     //INSIDE this function "string" is OK
     printf("string : '%s'\n", string);

     return string; //but after returning.. it is NULL? :)
}

// ..and the call-by-reference way to do it (prefered)
void getStringCallByRef(char* reference) {

    strcat(reference, "bla");
    strcat(reference, "/");
    strcat(reference, "blub");
    //INSIDE this function "string" is OK
    printf("string : '%s'\n", reference);
    //OUTSIDE it is also OK because we hand over a reference defined in MAIN
    // and not defined in this scope (local), which is destroyed after the function finished
}

编译时,您会收到[预期的]警告:

me@box:~$ gcc -o example.o example.c 
example.c: In function ‘getStringNoMalloc’:
example.c:58:16: warning: function returns address of local variable [-Wreturn-local-addr]
         return string; //but after returning.. it is NULL? :)
            ^~~~~~

基本上我们在这里讨论的是什么!

运行我的示例将产生以下输出:

me@box:~$ ./example.o 
string : 'bla/blub'
MALLOC ### a = bla/blub 
string : 'bla/blub'
NO MALLOC ### b = (null) 
string : 'bla/blub'
CALLBYREF ### c = bla/blub 

理论:

用户@phoxis已经很好地回答了这个问题。基本上可以这样想:在{}之间的所有内容都是局部作用域,因此按照C标准在外部是“未定义”的。 通过使用malloc,您从(程序范围)中获取内存,而不是从(函数范围)中获取 - 因此它在外部是“可见”的。 第二种正确的方法是按引用调用。在这里,您在父范围内定义变量,因此它使用栈(因为父范围是main())。

总结:

有三种方法可以做到这一点,其中一种是错误的。C语言有点笨拙,不能只是让一个函数返回动态大小的字符串。要么您必须malloc然后free它,要么您必须按引用调用。或者使用C++ ;)


1
感谢您提供清晰明了的答案。这帮助我理解并修复了我的错误。 - Michael

9

无需使用malloc或按引用调用。您可以在函数内部声明一个指针,并将其设置为想要返回的字符串/数组。

以@Gewure的代码为基础:

char *getStringNoMalloc(void){
    char string[100] = {};
    char *s_ptr = string;

    strcat(string, "bla");
    strcat(string, "/");
    strcat(string, "blub");
    //INSIDE this function "string" is OK
    printf("string : '%s'\n", string);

    return s_ptr; 
}

这段代码完美运行。

使用原始问题中的非循环版本代码:

char *foo(int x){    
    char a[1000];
    char *a_ptr = a;
    char *b = "blah";       

    strcpy(a, b);

    return a_ptr;
}

5
我对C语言完全不熟悉。根据之前的答案:使用引用调用会使用在调用者作用域中声明的变量,因此它会被保留,而使用malloc需要在使用后释放该内存以避免内存泄漏。这在这里如何应用?指针是在被调用函数的作用域内创建的,我们如何知道当我们想要读取它时所寻址的内存区域仍然未被触及?谢谢。 - Shyri
@Shyri 嗯,这是一个深奥的问题,特别是第二部分。我现在也在思考。你所问的基本上是堆如何确保其上的指针不被破坏? - Gewure
3
@Shyri 这段代码有点缺陷,因为你怀疑的是100%正确的。一旦函数返回,不能保证数组会被保留。尝试使用C++重写相同的代码。创建一个带有自定义析构函数的类。你会注意到,一旦函数作用域结束,析构函数就会被调用。但你仍然可以访问数组。在我看来,这是很危险的。 - arunanshub
2
@Shyri 请查看此代码示例 - arunanshub

3

a是函数内部的一个数组。一旦函数返回,它就不存在了,因此您不应该返回局部变量的地址。
换句话说,a的生命周期限定在函数的范围内({}),如果您返回指向它的指针,则得到的是指向无效内存的指针。这些变量也称为自动变量,因为它们的生命周期是自动管理的,您不需要显式地管理它。

由于您需要将变量扩展到函数范围之外以使其持久存在,因此您需要在堆上分配一个数组并返回指向它的指针。

char *a = malloc(1000); 

这样,数组a会一直驻留在内存中,直到您在相同地址上调用free()
不要忘记这样做,否则会导致内存泄漏。

3

这行内容:

char b = "blah";

出现了问题 - 您的lvalue需要是一个指针。

您的代码还存在堆栈溢出的风险,因为您的递归检查没有将x的减少值限定在一定范围内。

无论如何,您实际收到的错误消息是由于char a是自动变量; 一旦您return它就会停止存在。您需要使用其他变量类型而不是自动变量。


1

a 在函数内部被定义,无法在函数外部使用。如果你想从函数中返回一个 char 数组,你需要动态分配它:

char *a = malloc(1000);

在某个时刻调用返回指针上的free

你还应该在这一行看到一个警告:char b = "blah";:你试图将字符串字面量分配给char


1
所有回答都非常好地解释了这个问题。
然而,我想再添加另外一些信息。

我遇到了同样的问题,因为我想让函数的输出是一个向量。

在这种情况下,常见的解决方案是将输出声明为函数本身的参数。这样,变量的分配和存储所需的物理空间就在函数外进行管理。下面是一个伪代码来解释经典的解决方案:
void function(int input, int* output){
    //...
    output[0] = something;
    output[1] = somethig_else;
    //...
    return;
}

在这种情况下,问题中的示例代码应进行更改:
void foo(int x, char* a){
     if(x < 0){
        char b = "blah";
        //...
        strcpy(a, b);
        //..
        return;
      }
    //..
}

-1
char b = "blah"; 

应该是:

char *b = "blah"; 

这不会有任何影响。阅读我的回答,你就会明白为什么! - Gewure
4
@Gewure说:“this won't make a difference”(这不会有任何影响)。但实际上会有影响,因为字符串字面值并不存储在函数的栈上,而是“在整个程序生命周期内保持存在。它们具有静态持续时间寿命”。(参考来源:https://dev59.com/YWkw5IYBdhLWcg3wVpBi#9970305) - alk
@alk 你说得对,这有点奇怪! :) 这种写法相当于黑客式的和隐晦的。Jerry 应该在他的回答中解释一下! - Gewure

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接