分享

关于c ++:在DLL中分配内存并将其指向客户端应用程序的指针是不好的做法吗? | 码农家园

 wuxinit_ 2023-05-10 发布于湖北

我正在使用一个动态加载DLL的exe。 DLL中的函数在堆上分配内存,并将指向该内存的指针传递给exe。

一位长者说这样做是不好的做法。 他说,如果我必须在exe和DLL之间共享内存,则exe必须分配内存并将指向该DLL的指针传递给DLL,而不是相反。 这是真的? 为什么?

编辑:就我而言,我计划在DLL本身内部分配和取消分配内存。

  • 我以前从未听说过。我的印象是应用程序堆属于当前正在运行的进程。不管是调用DLL还是可执行文件,在堆上分配对象的工作方式都相同。但是也许我错了。
  • 请注意,这里有两类答案:"整洁的所有权语义"和" CRT链接问题"。这些答案均未必表明应分配/释放的exe文件而不是dll,但对于链接问题,提供相同的无关紧要的东西,而有人认为一个好的接口可能是调用方分配了(并且与dll中的被调用者无关)。因此,我认为您需要进一步质疑高年级学生(并且我认为高年级学生需要考虑一下它是否是一种无缘无故地传授规则的良好教学技巧)。
  • C ++ stdlib(按您的说法是dll)确实分配了内存(例如,在get_temporary_buffer()中),并将其返回给调用方(exe)。
  • @Steve:在这里向长者展示了评论和答案,他说从逻辑上讲,DLL(他称其为服务器)不应保存资源。资源属于客户端,因此客户端应进行分配。它是数据所有权的想法。在我的情况下,DLL使用的是第三方库,该库需要客户端按一定顺序向其提供某些特定信息,因此必须在DLL本身内创建并分配该结构。

这是让调用者提供指针的一些原因:

  • 对称所有权语义。其他答案已经解释了这一点。
  • 避免不匹配分配器和解除分配器。如Aesthete的答案中所述,如果DLL分配了一个指针并返回它,则调用者必须调用相应的释放器以释放它。这不一定是琐碎的:DLL可能与malloc / free的一个版本静态链接,而.exe与另一个版本的malloc / free链接。 (例如,当.exe使用专用调试版本时,DLL可能正在使用发行版。)
  • 灵活性。如果DLL是供一般使用的,则让调用方分配内存可以为调用方提供更多选项。假设调用者不想使用malloc,而是希望从某个特定的内存池中分配内存。也许在这种情况下,调用者可以提供一个指向堆栈上分配的内存的指针。如果DLL本身分配了内存,则调用者将没有任何这些选项。
  • (第二点和第三点通常也可以通过让.exe为DLL代码提供分配器/释放器来解决。)

    • 我在DLL本身内部进行分配和取消分配,因此分配不应该成为问题。但是我同意第3点,即调用者可以选择分配到特定的内存池中。
    • @Nav注意,灵活性伴随着付出的代价,使呼叫者的生活更加艰难。还有一个开放的问题,那就是什么更好,因为在相反的情况下,无论如何,您都可以优化DLL以使用内存池。而且库用户可能对您的库内部一无所知,因此他应根据哪种原则决定如何分配内存?如果用户必须为其分配每个结构,则复杂的库将无法使用。许多重要的库(例如Qt)可以自己管理内存,这完全可以。
    • 似乎提供分配器函数指针可以避免所有提到的技术问题,但是我很少看到它是可能的解决方案。很高兴知道我没有任何遗漏。有时,计算正确的分配大小可能会很复杂和/或递增,但是调用者宁愿不必对返回的缓冲区使用特殊的释放程序-类似于glibc / BSD asprintf()这样的例子。

    设计模式背后的基本思想之一就是所有权。这个想法是-one who creates a resource (and thereby holds it in the pointer) should be responsible for deleting the resource。这将确保设计的神圣性,并在更长的项目寿命中,其开发人员可以看到较少的错误。

    所以现在就您而言,DLL可以由任何可执行文件附加,并且他可以尝试删除资源,这可能会导致将来出现问题。因此,我认为反之亦然,我认为这是一个合理的建议。

    • 虽然所有权方面是一个很好的原则,但是这种回答类型忽略了.exe和.dll可能使用不同分配器的实际技术原因。例如,假设您使用的是.dll的发行版,而.exe的调试版本使用的是mallocfree的某些特殊调试版本。
    • 使用Microsoft crt,内存分配/释放在调试到发行之间是不兼容的。您不能安全地在调试dll中分配内存并在发行exe或viseversa中释放它。

    我之前已经看到过这个问题,它是由DLL和exe链接到CRT(静态,动态MT等)的方式不同引起的。

    我将在DLL和可执行文件之间传递指向内存的指针,它们都应提供某种Free()功能以从各自的堆中释放内存。


    通常,堆(一个堆)属于该进程,并且从何处进行分配都无关紧要,因此这将很好地工作,除非不是这样。

    因此,声称它是"不良做法"是有效的。除了能正常工作之外,几乎没有什么东西能比正常工作更糟糕。

    最糟糕的部分是,当一切都崩溃时,并不能立即清楚地指出出了什么问题,而您很容易在不知不觉中遇到问题。就像将特定版本的CRT链接到您的DLL一样简单。或者您的团队中的某人可能由于某种原因而创建了单独的堆。或者,导致立即创建另一个堆的其他原因(不是立即显而易见的)。

    导致错误重堆的情况如此恶毒的原因是,您通常不知道会发生什么事情,何时发生(或者是否有人会注意到)。

    您可能从堆函数或异常中获得NULL返回值。您的应用程序可能已经为它们中的一个准备好了,或者可能没有准备好(老实说,您总是检查返回值,不是吗?)。它可能在释放后立即崩溃,或者您可能只是默默地泄漏了内存并在数分钟或数小时后用完了地址空间(或内存),而没人知道原因。或者是其他东西。
    因此,您所看到的可能与造成问题的原因根本没有(明显的)相关性。


    我只想指出,将分配器传递给DLL来分配内存是完全安全的,并且是C ++ std库本身使用的标准模型。在这种情况下,分配是由DLL通过从调用方传递的方法完成的,避免传递指针,并避免链接到malloc()的不同实现的问题。


    exe和dll可能具有不同的堆。当任何一个尝试释放对方分配的内存时,释放都会失败,并且会发生泄漏。

    仅当exe和dll都使用相同版本的CRT动态使用CRT时,它们才使用相同的堆。

    因此,在同一二进制文件中进行分配和释放是一个很好的建议。

    • 我想对此进行更明确的说明:永远不要尝试在exe文件中进行分配,并在dll中进行释放,反之亦然。堆不属于应用程序或进程。它属于CRT。仅当两者都动态使用完全相同的CRT版本时,它才有效。但这是很薄的冰。如此紧张的尝试。

    正如@kumar_m_kiran所指出的,这与所有权问题有关,但是我想指出的是,@ aleguna也是正确的,因此,恕我直言,正确的规则是"在DLL或EXE中分配和取消分配相同的内存,但不能同时分配和分配相同的内存" 。


    我认为交出原始指针通常是不好的做法。如果是dll,它应返回一个具有自定义删除程序的智能指针,该删除程序可以适当地处理清除(按偏好顺序,选项为std::unique_ptrstd::shared_ptrboost::shared_ptr)。

    实际上,使用std::weak_ptrboost::weak_ptr可能更安全-这些文件要求您每次访问资源时都要检查一次,因为在此期间dll可能已被卸载。

    • 如果您的平台ABI指定了所讨论的共享指针的实现,那就很好。如果没有,则根本无法跨dll边界使用它们(或者,可以安排这样做,但是必须首先在调用的两侧绑定足够的实现细节,以确保它们匹配)。除此之外,建议同样适用于所有函数调用,dll中的这些函数无关的事实及其良好建议。但是,如果问题是CRT不匹配,则此建议会使问题变得更糟,而不是更好。
    • 哦,除了克服了CRT问题外,我的优先顺序为unique_ptrauto_ptrshared_ptrauto_ptr很难使用,并且被unique_ptr取代,但是它做得很好的一件事是在C ++ 03中信号了返回值的所有权。因此,如果您想掩盖发问者使用C ++ 03而不是C ++ 11的巨大可能性,那么值得一提;-)
    • 都很好。我在那里感到困惑。我曾经将自己从dll中获得的指针包装在智能指针中,并确保调用了正确的释放方法。这可以规避CRT问题,但是仍然要对客户端负责正确的资源管理。
    • 同意,并且dll作者的一个诀窍是在匿名名称空间中提供具有函数功能的头文件,该名称空间在边界的调用方进行包装,但使用dll作者维护的代码。如果所有权真正是在exe和dll之间共享的,那么它将变得有些棘手。我不记得自己做过这种情况,但是我想您可能在exe端有一个shared_ptr,其删除器会破坏dll端的shared_ptr。
    • 尽管答案的第一部分已经讨论过-我强烈不同意第二部分的弱指针。引用计数对象与对象本身位于同一堆中。如果对象内存以某种方式消失了,则引用计数对象也消失了,并且弱指针将在尝试转换为共享指针时访问无效的内存。弱指针不会提供额外的安全性。

    我会说不,这不是"坏习惯"。根据我的经验,只需要小心在正确的内存空间中释放该指针即可。我已经建立了一个图形引擎,可以在多个DLL中分配资产(每个DLL代表一个迷你游戏)。使用shared_ptr(不过当时是Boost,但我确定C ++ 11(或更新版本)std :: shared_ptr支持相同的语义)。我提供的功能将删除正确空间中的内存。在这一点上,主要的考虑是要确保在释放DLL之前先释放shared_ptrs。我现在不记得了,但是我们可能已经使用了DLL / DLL包装器所拥有的shared_ptrs的列表,并且指针的所有其他用法都是通过对该shared_ptr的weak_ptr实现的。


    这不一定是不好的做法,但它是危险的做法(并且可能是不好的)。您需要仔细考虑谁负责释放内存。该exe可能无法(或不应该)直接直接释放DLL的内存,因此大概在以后的某个日期将该指针传递回DLL。因此,现在我们在EXE和DLL之间来回传递指针,这不是很好。


      本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
      转藏 分享 献花(0

      0条评论

      发表

      请遵守用户 评论公约

      类似文章 更多