这个返回本地字符数组的遗留代码不是错误的吗?

Posted

技术标签:

【中文标题】这个返回本地字符数组的遗留代码不是错误的吗?【英文标题】:Isn't this legacy code which returns a local char array wrong? 【发布时间】:2011-01-26 09:31:40 【问题描述】:

我遇到了一些遗留代码,其中包含这样的函数:

LPCTSTR returnString()

    char buffer[50000];
    LPCTSTR t;

    /*Some code here that copies some string into buffer*/

    t = buffer;
    return t; 

现在,我强烈怀疑这是错误的。我尝试调用该函数,它确实返回了您希望它返回的字符串。但是,我真的不明白这是怎么发生的:char 数组不应该存储在堆栈上,因此在函数退出后被释放吗?如果我错了,它被存储在堆上,这个函数不是造成内存泄漏吗?

【问题讨论】:

是的,你是对的。您不应该从函数返回静态数组。 @nvl 即使t是动态分配的,它也需要一个重载的赋值运算符来正确复制缓冲区。 不是内存泄漏,只是“幸运的是它可以工作”。如果堆栈上分配的数组的内存被重用,那么你的数组中就会有垃圾。这是创建模糊错误的好方法。 @AraK: 如图所示的数组肯定不是“静态的”?如果是这样就更好了。 :) 这很奇怪,t 指针没有得到 malloc-ed,并且该函数似乎仍然有效。也许只是它正在访问的内存已被释放,但尚未损坏所以我仍然获取字符串...但是在这种情况下,我不应该收到内存访问冲突错误吗? 【参考方案1】:

你是对的,这不能保证有效;事实上,如果你真的在这个缓冲区中存储了一个 50,000 个字符的字符串,那么在此之后调用某个函数(调用函数,调用函数..),在几乎每个系统中,由于函数的堆栈帧被压入堆栈。

它似乎工作的唯一原因是陈旧的堆栈内存(在大多数系统上)在函数返回后没有被清除。

[编辑]为了澄清,它似乎正在工作,因为堆栈没有机会增长 50,000 字节。尝试将其更改为char buffer[10]; 并在returnString() 之后调用一些函数,您应该会看到它已损坏。

【讨论】:

【参考方案2】:

我同意这里最可能发生的是自动变量地址的错误返回,但是,我回应 KevenK,如果它是标签指定的 C++,则不能保证这一点。我们不知道 LPCTSTR 是什么。如果包含的标头包含以下内容怎么办:

(是的,我知道这是泄漏,不是重点)


class LPCTSTR
private:
  char * foo;

public:
  LPCTSTR & operator=(char * in)
    foo = strdup(in);
  

  const char * getFoo()
    return foo;
  


;

【讨论】:

好吧,如果是这样,代码仍然无法工作。您需要一个用户定义的复制构造函数来正确地将 LPCSTR 值复制出函数。 重点不在于这个特定示例如何管理内存的生命周期。这是为了表明您不能像大多数赞成的答案一样简单地假设 LPCTSTR 是 char * 的宏。 LPCTSTR 类的完全有效的实现使该函数以明确定义的方式运行。如果这是 C,当然。但是,问题被标记为 C++。【参考方案3】:

这是潜伏在您的代码中的危险错误。在 C 和 C++ 中,您不允许 返回指向函数中堆栈数据的指针。它会导致未定义的行为。我会解释原因。

C/C++ 程序通过将数据推入和推出程序堆栈来工作。当你调用一个函数时,所有的参数都被压入堆栈,然后所有的局部变量也被压入堆栈。当程序执行时,它可能会在函数中将更多项推入和弹出堆栈。在您的示例中, buffer 被推入堆栈,然后 t 被推入 堆。堆栈可能看起来像,

上一个堆栈 参数 (其他数据) 缓冲区(50000 字节) t(指针大小)

此时t在栈上,它指向缓冲区,缓冲区也在栈上。 当函数返回时,运行时将堆栈上的所有变量弹出,这 包括t、缓冲区和参数。在您的情况下,您返回指针 t,因此 在调用函数中复制它。

如果调用函数然后查看 t 指向的内容,它会发现 它指向堆栈上可能存在或不存在的内存。 (运行时弹出它 离开堆栈,但堆栈上的数据可能仍然存在巧合,也许不是)。

好消息是,这并非没有希望。有可以搜索的自动化工具 您的软件中的这些类型的错误并报告它们。它们被称为静态 分析工具。 Sentry 就是一个可以报告此问题的程序示例 一种缺陷。

【讨论】:

【参考方案4】:

您的代码表现出未定义的行为 - 在这种情况下,UB 是它似乎“工作”。如果要将数组存储在堆上,则需要使用 new[] 分配它。然后,函数的调用者将负责通过函数返回的指针将其删除。

【讨论】:

更好的是,返回一个 std::basic_string<TCHAR> 或类似的对象,该对象知道如何复制和释放它拥有的任何资源。【参考方案5】:

在 C/C++ 中,数组和指针之间几乎没有区别。所以声明:

t = 缓冲区;

实际上有效,因为“缓冲区”表示数组的地址。该地址不会显式存储在内存中,直到您将其放入 t (即缓冲区不是指针)。 buffer[n] 和 t[n] 将引用数组的相同元素。但是,您的数组是在堆栈上分配的,因此当函数返回时,内存被释放 - 不被清除。如果您在它被其他东西覆盖之前查看它,那么它看起来会很好。

【讨论】:

【参考方案6】:

虽然每个人都认为该行为是未定义的,并且在这种情况下似乎是正确的,但在这种情况下考虑其他可能性很重要。

例如,重载的operator=(const char*) 可能在后台分配需求内存。尽管 Microsoft typedef 不是这种情况(据我所知),但在这种情况下需要注意这一点。

然而,在这种情况下,它工作起来似乎很方便,而且这肯定不是保证的行为。正如其他人所指出的,这确实应该得到解决。

【讨论】:

这是不可能的 - 该函数返回一个 char *(以 LPCSTR 为幌子),并且您不能为指针重载 operator=()。 我知道在这种情况下这不是正在发生的事情。关键是在这样的情况下(尤其是对象分配或容器类),在评估正在发生的事情时要牢记这一重要特征。否则,看似糟糕的分配实际上可能是完全有效的。我的观点完全是针对如何检查这种情况,而不是描述在这种特定情况下发生了什么。【参考方案7】:

你是对的,代码是错误的。 :)

您应该学习 C/C++ 中的内存分配。数据可以驻留在两个区域:堆栈和堆。局部变量存储在堆栈中。 malloced 和newed 数据存储在堆中。堆栈状态是函数本地的 - 变量存在于堆栈框架中 - 函数返回时将被释放的容器。所以指针被破坏了。

堆是全局的,所以所有数据都存储在那里,直到程序员明确地deleted 或freed。您可以依赖该区域。

【讨论】:

【参考方案8】:

没有内存泄漏,但函数仍然是错误的 - buffer 确实是在堆栈上创建的,调用者能够读取它的事实只是运气(它只能在调用 @987654322 之后立即访问@函数。)该缓冲区可能会被任何进一步的函数调用或其他堆栈操作覆盖。

将数据向上传递到调用链的正确方法是提供缓冲区和大小给要填充的函数。

【讨论】:

【参考方案9】:

此代码返回一个指向在堆栈上分配的内存的指针。 这是非常危险的,因为如果您尝试将此指针传递给另一个函数,内存将被第二个函数调用覆盖。

除此之外,您可以使用静态缓冲区:

static char buffer[50000];

未在堆栈上分配,因此指向它的指针仍然有效。 (这显然不是线程安全的)。

【讨论】:

【参考方案10】:

你的感觉是对的;代码非常错误。

内存确实在栈上,随着函数结束而消失。

如果缓冲区是静态的,那么您至少可以期望它一次只用于一个调用(在单线程应用程序中)。

【讨论】:

以上是关于这个返回本地字符数组的遗留代码不是错误的吗?的主要内容,如果未能解决你的问题,请参考以下文章

字符串数组是可变的吗?

从C中的字符串/字符数组中删除空格的函数

UUID不是128位长的吗,怎么生成的都是36个字符的字符串?

怎么判断一个对象是不是数组类型?

理解 C++ 中的 <string> 字符串数组

c语言,查找数组中是不是存在某个数?