此C代码是否会导致内存泄漏(新的广播数组)

此C代码是否会导致内存泄漏(新的广播数组)

Will this C++ code cause a memory leak (casting array new)

我一直在研究一些使用可变长度结构(TAPI)的遗留C代码,其中结构大小将取决于可变长度字符串。这些结构是通过强制转换数组new来分配的,因此:

1
STRUCT* pStruct = (STRUCT*)new BYTE[sizeof(STRUCT) + nPaddingSize];

以后,使用delete调用释放内存:

1
delete pStruct;

数组new[]和非数组delete的这种混合会导致内存泄漏,还是取决于编译器?我最好将这段代码更改为使用mallocfree吗?


从技术上讲,我相信这可能会导致分配器不匹配,但实际上我不知道有哪个编译器无法在此示例中执行正确的操作。

更重要的是,如果STRUCT在何处(或曾经被提供)一个析构函数,则它将调用该析构函数而不调用相应的构造函数。

当然,如果您知道pStruct来自何处,为什么不将其强制转换为delete以匹配分配:

1
delete [] (BYTE*) pStruct;

我个人认为您最好使用std::vector来管理内存,因此您不需要delete

1
2
std::vector<BYTE> backing(sizeof(STRUCT) + nPaddingSize);
STRUCT* pStruct = (STRUCT*)(&backing[0]);

一旦支持退出范围,您的pStruct不再有效。

或者,您可以使用:

1
2
boost::scoped_array<BYTE> backing(new BYTE[sizeof(STRUCT) + nPaddingSize]);
STRUCT* pStruct = (STRUCT*)backing.get();

boost::shared_array(如果需要转移所有权)。


代码的行为未定义。您可能很幸运(也可能不是很幸运),并且可以与您的编译器一起使用,但这确实不是正确的代码。它有两个问题:

  • delete应该是数组delete []
  • delete应该在指向与分配的类型相同的类型的指针上调用。
  • 因此,要完全正确,您想做这样的事情:

    1
    delete [] (BYTE*)(pStruct);

    是的,这将导致内存泄漏。

    除了C Gotchas之外,请参见以下内容:http://www.informit.com/articles/article.aspx?p=30642,为什么。

    Raymond Chen解释了向量newdelete与Microsoft编译器的幕后标量版本有何不同……...:
    http://blogs.msdn.com/oldnewthing/archive/2004/02/03/66660.aspx

    恕我直言,您应该将删除内容修复为:

    1
    delete [] pStruct;

    而不是切换到malloc / free,仅是因为这是进行更简单的更改而不会犯错;)

    当然,由于原始分配中的强制转换,我上面显示的更简单的更改是错误的,应该为

    1
    delete [] reinterpret_cast<BYTE *>(pStruct);

    所以,我想毕竟毕竟切换到malloc / free可能很容易;)


    C标准明确规定:

    1
    2
    3
    delete-expression:
                 ::opt delete cast-expression
                 ::opt delete [ ] cast-expression

    The first alternative is for non-array objects, and the second is for arrays. The operand shall have a pointer type, or a class type having a single conversion function (12.3.2) to a pointer type. The result has type void.

    In the first alternative (delete object), the value of the operand of delete shall be a pointer to a non-array object [...] If not, the behavior is undefined.

    delete pStruct中的操作数的值是指向char数组的指针,与其静态类型(STRUCT*)无关。因此,任何有关内存泄漏的讨论都是毫无意义的,因为代码格式不正确,在这种情况下,不需要C编译器来生成明智的可执行文件。

    它可能会泄漏内存,它不会泄漏内存,或者它可能会导致系统崩溃。确实,我测试过您的代码的C实现在delete表达式处中止了程序执行。


    如其他帖子中所强调的:

    1)调用new / delete分配内存,并可以调用构造函数/析构函数(C '03 5.3.4 / 5.3.5)

    2)将newdelete的数组/非数组版本混合在一起是未定义的行为。 (C '03 5.3.5 / 4)

    从源头上看,似乎有人进行了搜索并替换了mallocfree,以上就是结果。 C确实可以直接替换这些函数,也就是说直接调用newdelete的分配函数:

    1
    2
    3
    4
    STRUCT* pStruct = (STRUCT*)::operator new (sizeof(STRUCT) + nPaddingSize);
    // ...
    pStruct->~STRUCT ();  // Call STRUCT destructor
    ::operator delete (pStruct);

    如果应调用STRUCT的构造函数,则可以考虑分配内存,然后使用放置new

    1
    2
    3
    4
    5
    BYTE * pByteData = new BYTE[sizeof(STRUCT) + nPaddingSize];
    STRUCT * pStruct = new (pByteData) STRUCT ();
    // ...
    pStruct->~STRUCT ();
    delete[] pByteData;

    n


    n


    如果您确实必须执行此类操作,则可能应该直接调用运算符new

    1
    STRUCT* pStruct = operator new(sizeof(STRUCT) + nPaddingSize);

    我相信以这种方式调用可以避免调用构造函数/析构函数。


    这是您要引用的数组删除([]),而不是向量删除。
    一个向量是std :: vector,它负责删除其元素。


    我目前无法投票,但slicedlime的答案比Rob Walker的答案更好,因为问题与分配器或STRUCT是否具有析构函数无关。

    还请注意,示例代码不一定会导致内存泄漏-这是未定义的行为。几乎任何事情都可能发生(从没坏到很远的崩溃)。

    示例代码导致未定义的行为,简单明了。 slicedlime的回答很直接,很直率(要注意的是,由于向量是STL,因此应将"向量"一词更改为"数组")。

    C FAQ(第16.12、16.13和16.14节)很好地介绍了此类内容:

    http://www.parashift.com/c -faq-lite / freestore-mgmt.html#faq-16.12


    是的,因为您可以使用new []进行分配,但可以使用delelte进行取消分配,所以在这里,malloc / free是更安全的,但是在c中,您不应使用它们,因为它们将不处理(de)构造函数。

    您的代码也将调用解构函数,但不会调用构造函数。对于某些结构,这可能会导致内存泄漏(如果构造函数分配了更多的内存,例如用于字符串)

    更好的做法是正确执行,因为这也会正确调用任何构造函数和反构造函数

    1
    2
    3
    STRUCT* pStruct = new STRUCT;
    ...
    delete pStruct;

    您可以将其回退到BYTE *并删除:

    1
    delete[] (BYTE*)pStruct;

    n


    始终最好保持对任何资源的获取/释放尽可能平衡。
    尽管在这种情况下很难说是否泄漏。这取决于编译器对向量(取消)分配的实现。

    1
    2
    3
    4
    5
    6
    7
    BYTE * pBytes = new BYTE [sizeof(STRUCT) + nPaddingSize];

    STRUCT* pStruct = reinterpret_cast< STRUCT* > ( pBytes ) ;

     // do stuff with pStruct

    delete [] pBytes ;

    您正在混合C和C的做事方式。为什么分配的资源超过STRUCT的大小?为什么不只是"新STRUCT"?如果必须执行此操作,则在这种情况下使用malloc和free可能会更清楚,因为那样的话,您或其他程序员可能不太可能对分配的对象的类型和大小进行假设。


    @马特·克鲁克申克

    "好吧,在VS2005上进行实验,我无法从vector new进行的内存标量删除中得到诚实的泄漏。我猜这里的编译器行为是"未定义的",是我可以召集的最佳防御方法。"

    我不同意这是编译器行为,甚至是编译器问题。正如您所指出的那样,关键字" new"将被编译并链接到运行时库。这些运行时库以独立于OS的一致语法处理对OS的内存管理调用,并且这些运行时库负责使OS,Linux,Windows,Solaris,AIX等操作系统之间的malloc和新工作保持一致。这就是我提到可移植性参数的原因;试图向您证明运行时实际上也不管理内存。

    操作系统管理内存。

    操作系统的运行时库接口。在Windows上,这是虚拟内存管理器DLL。这就是为什么在GLIB-C库而不是Linux内核源代码中实现stdlib.h的原因。如果在其他操作系统上使用GLIB-C,则将执行malloc更改以进行正确的OS调用。在VS,Borland等中,您将永远找不到与其实际管理内存的编译器一起提供的任何库。但是,您将找到malloc的特定于操作系统的定义。

    由于我们拥有Linux的源代码,因此您可以查看一下malloc在其中的实现方式。您将看到malloc实际上是在GCC编译器中实现的,该编译器反过来基本上在内核中进行了两个Linux系统调用来分配内存。永远不要,malloc本身,实际上是在管理内存!

    别从我这里拿走它。阅读源代码到Linux OS或您可以看到什么K


    使用运算符new和delete:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    struct STRUCT
    {
      void *operator new (size_t)
      {
        return new char [sizeof(STRUCT) + nPaddingSize];
      }

      void operator delete (void *memory)
      {
        delete [] reinterpret_cast <char *> (memory);
      }
    };

    void main()
    {
      STRUCT *s = new STRUCT;
      delete s;
    }

    @马特·克鲁伊克(Matt Cruikshank)
    您应该注意并再次阅读我写的内容,因为我从不建议不要调用delete [],而只是清理操作系统。而且您对管理堆的C运行时库是错误的。如果真是这样,那么C将不会像今天那样具有可移植性,并且崩溃的应用程序也永远不会被OS清除。 (确认存在特定于操作系统的运行时,使C / C显得不可移植)。我挑战您从kernel.org的Linux源代码中找到stdlib.h。实际上,C语言中的new关键字正在与malloc讨论相同的内存管理例程。

    C运行时库进行OS系统调用,并且由OS管理堆。您的部分正确之处在于,运行时库指示何时释放内存,但是,它们实际上并没有直接遍历任何堆表。换句话说,您链接的运行时不会将代码添加到您的应用程序中以遍历堆进行分配或取消分配。在Windows,Linux,Solaris,AIX等中就是这种情况。这也是为什么您不会在任何Linux内核源代码中对malloc进行优化,也不会在Linux源代码中找到stdlib.h的原因。了解这些现代操作系统的虚拟内存管理器会使事情变得更加复杂。

    是否想知道为什么可以在1G盒子上调用2g RAM的malloc并仍然返回有效的内存指针?

    使用三个表在内核空间内管理x86处理器上的内存管理。 PAM(页面分配表),PD(页面目录)和PT(页面表)。这是我所说的硬件级别。 OS内存管理器(而不是C应用程序)要做的一件事是,在引导期间借助BIOS调用找出盒中安装了多少物理内存。操作系统还处理异常,例如当您尝试访问内存时,您的应用程序也没有权限。 (GPF一般保护故障)。

    也许我们在说同样的话Matt,但是我想您可能会稍微混淆一下引擎盖下的功能。我过去经常维护一个C / C编译器...


    @ericmayo-薄饼。好吧,尝试使用VS2005,我无法从vector new进行的内存中标量删除中得到诚实的泄漏。我猜编译器的行为在这里是"未定义的",这是我可以召集的最佳防御方法。

    您必须承认,按照原始海报所说的那样做确实很糟糕。

    If that were the case then C++ would
    not be portable as is today and a
    crashing application would never get
    cleaned up by the OS.

    不过,这种逻辑并没有真正成立。我的断言是,编译器的运行时可以管理操作系统返回给它的内存块中的内存。大多数虚拟机就是这样工作的,因此在这种情况下您对可移植性的争论没有多大意义。


    我认为这不是内存泄漏。

    1
    STRUCT* pStruct = (STRUCT*)new BYTE [sizeof(STRUCT) + nPaddingSize];

    这将转换为操作系统中的内存分配调用,并在该调用中返回指向该内存的指针。在分配内存时,将知道sizeof(STRUCT)的大小和nPaddingSize的大小,以便满足针对底层操作系统的任何内存分配请求。

    因此,已分配的内存被"记录"在操作系统的全局内存分配表中。内存表由它们的指针索引。因此,在相应的delete调用中,最初分配的所有内存都是可用的。 (内存碎片化也是该领域的热门主题。)

    您会看到,C / C编译器不管理内存,而底层操作系统是。

    我同意有更干净的方法,但是OP确实说这是旧代码。

    简而言之,我没有看到内存泄漏,因为公认的答案认为会有一个。


    Len:问题在于pStruct是STRUCT *,但是分配的内存实际上是某种未知大小的BYTE []。因此delete [] pStruct不会取消分配所有已分配的内存。


    Rob Walker的回复很好。

    另外,如果您没有任何构造函数或/和析构函数,那么您基本上需要分配和释放大量原始内存,请考虑使用free / malloc对。


    ericmayo.myopenid.com太错了,以至于有人应声望他。

    C或C运行时库正在管理由操作系统按块分配给它的堆,就像您指出的那样,Eric。但是开发人员有责任向编译器指示应进行哪些运行时调用以释放内存,并可能破坏那里的对象。在这种情况下,必须使用向量删除(aka delete []),才能使C运行时将堆置于有效状态。当PROCESS终止时,操作系统足够智能以释放基础内存块这一事实并不是开发人员应该依靠的。这就像根本不调用delete一样。


    推荐阅读