返回字符指针的函数

6

我在一个遗留应用程序中遇到了很多返回char指针的函数。 其中一些返回指向本地字符数组的指针。它似乎在多次调用后(不是立即)导致崩溃,请看下面的用法:

char *f1(){
  char buff[20];
  char *ptr;

  ----
  ----
  ptr=buff;
 return ptr;
}

---
---

f2(f1());

f1()返回一个指向本地变量的指针,然后将其传递给另一个函数。在MS DEV中使用_DEBUG模式编译时,它会直接崩溃。但在发布模式下,它不会立即崩溃,但在进行大量此类调用后可能会发生崩溃。

当我将使用方式修改为以下内容时,它可以正常工作且没有任何问题。以下用法是否安全?

strcpy(arr,f1()); /* arr is fixed char array*/
f2(arr);
12个回答

13

不,这是未定义的行为。它在你的情况下恰好起作用,但随时可能停止工作。


它能够正常工作的原因是在 x86 上,buff 数组是从较低的地址填充到较高的地址,并且可能不会被 strcpy 的堆栈使用所触及。无论如何都不要这样做:由于信号和中断等异步事件,堆栈使用可能会有所变化。 - Richard Pennington

3

不,这并不安全。仅仅调用strcpy就足以修改堆栈,从而可能在后面引起问题,因为返回地址和参数可能会覆盖数组。


3

除了需要在函数使用后释放内存(在外部),malloc解决方案很有趣。否则会导致内存泄漏。


2

下面的用法安全吗?

不安全。

如果您的函数返回一个指针,请确保它分配了一个内存区域并将指针返回给它:

char *safe_f1(void) {
    char *ptr;
    ptr = (char *) malloc(20 * sizeof(char));
    ...
    return ptr;
}

2
不安全的原因很简单:函数中的任何变量都将分配在堆栈上,而堆栈的内存在函数返回后被释放。内存被释放并不意味着其内容被更改。
这意味着您放入变量char buff [20]的内存内容仍然位于buff或ptr的内存位置(因为ptr = buff)。每当调用另一个函数(或执行另一个块)时,其函数/块变量也会进入堆栈,从而可能更改指针ptr指向的内存内容。
在您编写的strcpy示例中,您足够幸运,使strcpy函数变量没有进入旧的buff数组内部的位置。这就是您得出它很安全的印象的原因。
结论是:您无法保证堆栈中释放的内存内容在两个函数调用之间不会更改。
解决方案是使用malloc,因为malloc不会在堆栈上分配内存,而是在堆上分配内存。堆内存不会被释放,除非您选择这样做(通过free调用)。
这种方法可以保证指针ptr指向的内存在其他任何函数中都是安全可用的。
这种解决方案的缺点是固有的:一旦不释放内存,除非您编程释放该内存并且忘记释放此内存并且失去指针ptr的内容,否则该内存将一直存在,为您的程序分配但永远无法实现,一直到程序运行结束。这种情况成为内存泄漏:-)
这就是为什么有些语言需要垃圾回收器的原因......但这是另一个故事:-)
附注:我认为这是安全的(如果您的程序是单线程),尽管我不建议这样做:
{
  char safe_buffer[20];
  char *unsafe_ptr;
  int i;
  unsafe_ptr = f1();
  /*Copy the buffer without calling any function
    not to change the stack content
  */
  for(i=0;i<20 && *(unsafe_ptr + i) != 0;i++)
  {
    *(safe_buffer + i) = *(unsafe_ptr + i);
  }
  *(safe_buffer + i) = 0;
  f2(safe_buffer);
}

2

f1函数返回一个临时变量(buff),该变量在函数返回时被释放。您需要在函数内部使用malloc()。


这几乎肯定会导致内存泄漏,并存在一些未被发现的对该函数的引用。 - David Sykes
当然,您可以要求预先分配的内存指针作为输入参数,并让函数填充它,但这样我们就不再有相同的函数签名了。 - Zitrax

2

永远不要返回指向本地变量的指针。在某些情况下可能会起作用,但一般情况下会遇到很多问题。

替代方法:

  • 记录函数返回指向已分配内存的指针,并且调用者必须释放返回的缓冲区
  • 添加缓冲区和大小参数,并填充缓冲区(这通常是在Win32 API中完成的)
  • 如果使用C ++,请使用std :: string

我认为有两种解决方案。
  1. 在f1()内部使用malloc。在这种情况下,所有调用者都应该在使用后正确释放内存。应避免像f2(f1())这样的用法,因为它不会返回用于释放内存的句柄。
  2. 将分配的指针传递给函数,以便f1()可以将文本复制到其中。在这种情况下,现有的用法如f2(f1())也不可能。
你更喜欢哪个解决方案?无论如何,这需要对现有应用程序进行大量更改,因为这样的模式存在于多个地方。其中一些分配内存,但没有释放它。像f1(f2(f3()))这样的用法确实存在。
- cobp
如果你所说的“永不”是指“除非本地变量是静态的”,那么我同意。 - William Pursell
@William,如果本地变量是静态的话,它可以工作(至少更长时间)。但这并不意味着这是一个好习惯。如果在您制作返回缓冲区的个人副本之前重新执行函数,则仍将遇到问题。多线程应用程序也是如此(尽管您可以为此使用Thread-Local-Storage变量)。 - Patrick

1

不行,它仍然不安全。

当你执行 strcpy(arr,f1()); 时,作为第二个参数使用的指针已经指向一个不存在的数组。


1
不,你看`buff [20]`只在`f1`函数内部可用。准确地说,该内存是在`f1`的堆栈上分配的。
你需要使用`malloc`在堆上创建一个新的`buff [20]`,并从`f1`内部返回指向该内存的指针。另一种方法是在`f1`之外(从调用`f1`的函数)创建`buff [20]`,并将其作为参数发送到`f1`。然后,`f1`可以更改缓冲区的内容,并且甚至不必返回它。

0
其实,最好的方法是修改f1()函数来使用malloc()。你的解决方案并没有接近定义行为。

1
好的,那么所有调用f1()的函数在使用完后应该适当地释放它。对吗? - cobp

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