C警告:函数返回局部变量的地址

5
下面的函数接受包含应用程序调用路径的argv [0]参数,并替换最后一位直到遇到“ /”,并使用位于同一文件夹中的新应用程序的名称生成。

顺便说一下:我声明了一个全局argv变量,以便函数可以访问它,因为我不想在每个函数调用中传递信息。

当我编译代码时,所有似乎都可以工作,但我会收到上述警告。

我知道我正在声明变量,并且一旦函数返回,它将被销毁。

作为初学者C程序员,我想知道解决此问题的最优雅/最简单的方法是什么?

我应该将指针传递给函数或分配一些内存吗?

char *returnFullPath()
{
    char pathToApp[strlen(argv[0])+1];
    strcpy(pathToApp, argv[0]);
    int path_length = strlen(argv[0]);

    while (pathToApp[path_length] != '/')
    {
        path_length--;
    }

    if (path_length > 2)
        pathToApp[path_length+1] = '\0';
    else
        pathToApp[0] = '\0';

    // length of getcwd + length of pathtoapp + 1 for zero plus 6 for "bidbot"
    char bidbotPath[strlen(getcwd(NULL,0)) + strlen(pathToApp) + 1 + 6];

    sprintf(bidbotPath, "%s/%sbidbot", getcwd(NULL,0), pathToApp);

    return bidbotPath;
}
8个回答

48
一些其他的答案建议你malloc一些东西并返回它。这种做法与C++中在函数中new一些东西然后让调用者删除它(谁拥有所有权?)是同样的不良实践。
许多C API具有如下格式的原因:
function(buf, length);

这意味着调用方提供缓冲区以及其长度。调用方有责任分配和释放此缓冲区,您的函数应该使用它,并检查不会溢出长度。

不要动态分配内存并返回。这只会引来麻烦。


6
当呼叫者无法预知需要分配多大的缓冲区时,这个好建议就不适用了。 - David Heffernan
3
很多C库和函数,包括POSIX函数,为了David Heffernan所说的原因而进行分配并返回。更好的建议是:“不要对调用约定做出假设。”与“使用内存调试器”相匹配。 - jmtd
@David Heffernan,是的,我知道这个问题-但在原始问题的背景下,MAX_PATH应该足够了 :) - Moo-Juice
Windows路径可以比MAX_PATH更长。 - David Heffernan
1
@David:好的,可能会有例外情况,但我认为上面的答案应该是普遍规则。在大多数情况下,我提到的策略确实有效。顺便说一句,如果函数进行分配,它必须也知道大小。 - Rudy Velthuis
显示剩余3条评论

4
替换
char bidbotPath[strlen(getcwd(NULL,0)) + strlen(pathToApp) + 1 + 6];

使用

char* bidbotPath = malloc(strlen(getcwd(NULL,0)) + strlen(pathToApp) + 1 + 6);

这样,您的变量将分配在堆上而不是栈上,因此在函数返回后它不会被删除。


1
他/她要求使用C语言,而不是C++。 - Diego Sevilla
谢谢Luchian,我还在学习malloc。我尝试了你的代码,它可以工作。这是我之前自己创建的代码:"char * bidbotPath = malloc((sizeof(char))*(strlen(getcwd(NULL,0)) + strlen(pathToApp) + 1 + 6));" 你的代码看起来肯定更干净。但是你为什么不使用sizeof运算符呢?我以为在malloc时必须使用它?再次感谢你的澄清! - Frank Vilea
1
我没有使用sizeof,因为char的大小始终为1。这是标准保证的。因此,malloc(sizeof(char)*(strlen(getcwd(NULL,0)) + strlen(pathToApp) + 1 + 6))中使用sizeof是多余的。 - Luchian Grigore
如果您正在尝试返回一个在堆上的变量,那么在函数完成后如何从堆中释放该变量?如果未清除堆中的变量,则在几个循环后程序将会冻结(如果您正在使用微控制器)。 - Goutham Rapol

3
如果可能的话,最好将指向返回值所写入的内存的指针作为函数的参数。我这么说是因为你允许客户端(即函数的调用者)选择内存的位置:堆栈、堆或者更奇特的位置。
现在,问题就在于“如果可能”的限制条件。有时候只有在实现函数时才能确定内存的大小。一个典型的例子是空字符结尾字符串的函数。当你遇到这种情况时,最好在函数内部分配堆上的内存,并要求客户端在使用完毕后释放该内存。

3
作为首先要说的事情,你收到的警告可能被认为是一个错误。任何后续调用新函数的操作都将不可避免地覆盖原本保存所需返回信息的内存。话虽如此,有几种方法可以解决这个问题。

客户端所有权

一种方法是像Moo-Juice建议的那样,在调用时添加一些参数,将使信息在函数调用后保持持久性。
void returnFullPath(char* fullPath, int maxLength)

在完成之前,使用调用strncpy(http://www.cplusplus.com/reference/cstring/strncpy/)将结果复制到输出参数中。请注意,保留HTML标签。
strncpy(fullPath, bidbotPath, maxLength);

这样,您可以确保函数调用者是内存的所有者,分配和释放它。并且您不会尝试使用未分配的内存。
提供方拥有权
然而,对于这种语言,还有另一种方法也被接受。例如,stdio.h库使用的就是这种方法。如果要打开文件,则使用结构FILE作为指针。在这种情况下,stdio提供了两个函数fopen和fclose,一个分配资源,另一个释放资源。这利用了一个称为抽象数据类型(ADT)的概念,这是结构化编程中最接近对象的东西。有关ADT的详细信息,请参见this。在这种情况下,完整的ADT似乎对您正在做的事情来说过于复杂,但与这个想法相符合。
对于此案例,将需要分配和释放两个函数。
char* getFullPath(); /* here is where you do malloc*/
void disposeFullPath(char* fullPath); /* and here, free */

这样,您就可以动态分配所需的确切内存量。

关于你的问题,我想发表一些评论。

  • 尽可能遵循 ANSI 标准。这里是维基百科,看起来比较准确。
  • 现在你正在使用 C 语言,应该检查语言的样式约定。请查看这里
  • 使用 strrchar 查找路径中最后一个 '/':在这里
  • 最后但并非最不重要的:避免使用静态全局变量,它们只会带来麻烦。

2
当函数返回时,局部变量会被释放(回收),内存将用于其他用途。如果您返回局部变量的地址,则可能(并且应该)会导致问题。
有两种解决方法。
1)使用静态变量。静态局部变量在函数退出时不会被释放。
static char bidbotPath[....];

但是!它不能处理可变长度。
2) 使用 malloc
char *bidbotPath = malloc(strlen(getcwd(NULL,0)) + strlen(pathToApp) + 1 + 6);

在所有使用完毕之后,您应该调用free(bidbotPath)


1
可以返回指向static变量的指针,但是每当调用该函数时,使用该指针的所有人所指向的值都会改变。这很少是你想要的,甚至更少是你应该想要的。 - user395760
@delnan,您是否建议最好将指针传递给该函数? - Frank Vilea
@Frank:不,我的意思是使用static变量并不是一个解决方案。传递一个指针进行填充并返回一个新分配的指针位置是两种有效的解决方案,甚至可能还有更多。 - user395760

0
你需要使用 malloccalloc 动态分配内存来为变量 bidbotPath 分配内存。然后,确保调用你的函数的代码实际上释放了你返回的 malloc'ed 内存。这是一种常见的做法,也是 C 函数返回指向“生成”数组的指针的常见习语。
char * bidbotPath = (char*)malloc(strlen(getcwd(NULL,0)) + strlen(pathToApp) + 1 + 6);

0

BidotPath是在函数体范围内声明为普通堆栈变量的,因此当函数返回时它将消失。虽然你的程序现在可能能够工作,但这只是运气而已,如果其他代码在调用者之前重用旧的堆栈区域,它可能会在以后开始失败。

你可以声明bobotPath为静态变量,这样它就会保留下来,但会导致函数不具备线程安全性。你可以对适当长度进行malloc并返回以保持函数的线程安全性,但是调用者需要释放内存以避免泄漏。最好的方法是在函数参数中提供一个char数组和长度,以便将数据放入其中。在这里可以考虑使用snprintf()。在内部使用strncpy()和类似的例程将其复制到目标中,但要注意strncat()对你来说可能不太安全。

此外,你的代码需要应对argv[0]中可能没有斜杠的情况...只有可执行文件的名称。

这不完全是你所需要的,但这是我曾经使用过的一些代码。我把它留给学生去练习获取他们所需的内容:

cp = strrchr(argv[0], '/');
if (cp)
    cp++;
else
    cp = argv[0];

-1
当您尝试调用任何函数时,堆栈上会自动分配内存,但通常在函数定义执行后,堆栈帧会从堆栈内存中丢弃,特别是如果您希望函数返回地址,则可以将在函数定义中使用的变量设置为静态。

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