【问题标题】:C++ strings - How to avoid obtaining invalid pointer?C++ 字符串 - 如何避免获取无效指针?
【发布时间】:2014-05-27 00:26:20
【问题描述】:

在我们的 C++ 代码中,我们有自己的字符串类(出于遗留原因)。它支持c_str() 方法,很像std::string。我注意到许多开发人员都在错误地使用它。我已将问题简化为以下行:

 const char* x = std::string("abc").c_str();

std::string 上的析构函数在调用c_str() 后立即被调用的意义上来说,这段看似无辜的代码非常危险。结果,您持有一个指向已释放内存位置的指针。

这是另一个例子:

  std::string x("abc");
  const char* y = x.substr(0,1).c_str();

在这里,我们也使用了一个指向解除分配位置的指针。

这些问题在测试过程中不容易发现,因为内存位置仍然包含有效数据(尽管内存位置本身是无效的)。

我想知道您是否对我如何修改类/方法定义以使开发人员永远不会犯这样的错误有任何建议。

【问题讨论】:

  • 这里也有类似的讨论:stackoverflow.com/questions/10540157/…
  • 解决方案是让开发者在调用c_str()之前慎重考虑,不要在代码中使用char *。也许您可以回顾一下他们为什么在代码中使用指针而不是仅使用字符串,并建议其他解决方案。

标签: c++ stl


【解决方案1】:

代码的现代部分不应该处理这样的原始指针。 仅在为采用 const char* 的旧函数提供参数时才调用 c_str。喜欢:

legacy_print(x.substr(0,1).c_str())

为什么要创建const char* 类型的局部变量?即使您编写了一个复制版本c_str_copy(),您也会更加头疼,因为现在客户端代码负责删除生成的指针。

如果您需要将数据保留更长时间(例如,因为您想将数据传递给多个遗留函数),那么只需将数据始终包装在字符串实例中。

【讨论】:

    【解决方案2】:

    对于基本情况,您可以在“this”对象上添加一个 ref 限定符,以确保 .c_str() 永远不会立即临时调用。当然,这并不能阻止它们存储在指针之前离开作用域的变量中。

    const char *c_str() & { return ...; }
    

    但更全面的解决方案是将代码库中的所有函数从采用“const char *”替换为采用您的字符串类之一的函数(至少,您需要两个:一个拥有的字符串和一个借用的字符串slice) - 并确保您的字符串类中没有一个 cannot 是从“const char *”隐式构造的。

    【讨论】:

    • 如果提供了const& 变体,则需要提供已删除的&& 变体。而VS2013不处理它。但我喜欢它在编译时防止这种情况发生。
    • 请原谅我的无知,但是您如何在不修改字符串头文件本身的情况下添加它?
    • @LordAro 为什么不修改它呢? OP 声明他们使用的是自己的字符串类,而不是 std::string
    【解决方案3】:

    最简单的解决方案是更改您的析构函数,以便在销毁时在字符串的开头写入空值。 (或者,用错误消息或 0 填充整个字符串;您可以有一个标志来禁用此功能以用于发布代码。)

    虽然它不能直接防止程序员使用无效指针的错误,但当代码没有做它应该做的事情时,它肯定会引起人们的注意。这应该可以帮助您清除代码中的问题。

    (正如您所提到的,目前错误并没有被注意到,因为在大多数情况下,代码会在无效内存的情况下愉快地运行。)

    【讨论】:

    • 我认为我会将其替换为 '#' 或其他不寻常的东西,这样更容易在日志中发现。好主意。
    【解决方案4】:

    考虑使用 Valgrind 或 Electric Fence 来测试您的代码。这些工具中的任何一个都应该可以轻松地立即发现这些错误。

    【讨论】:

      【解决方案5】:

      如果您警告人们不正确地使用您的图书馆,我不确定您能做些什么。考虑实际的 stl string 库。如果我这样做:

      const char * lala = std::string("lala").c_str();
      std::cout << lala << std::endl;
      const char * lala2 = std::string("lalb").c_str();
      std::cout << lala << std::endl;
      std::cout << lala2 << std::endl;
      

      我基本上是在创建未定义的行为。如果我在 ideone.com 上运行它,我会得到以下输出:

      lala
      lalb
      lalb
      

      很明显原来lala的内存已经被覆盖了。我只想在文档中向用户明确说明这种编码是不好的做法。

      【讨论】:

        【解决方案6】:

        您可以删除 c_str() 函数,而是提供一个函数,该函数接受对已创建的空智能指针的引用,该指针将智能指针的值重置为字符串的新副本。这将迫使用户创建一个非临时对象,然后他们可以使用该对象来获取原始 c 字符串,并且在退出方法范围时它将被破坏并释放内存。

        这假设您的库及其用户将共享同一个堆。

        编辑

        更好的是,为此目的创建您自己的智能指针类,其析构函数调用库中的库函数来释放内存,以便跨 DLL 边界使用它。

        【讨论】:

        • 举个例子说明如何删除 c_str 函数?
        • 我以为他是想修改类定义,我认为c_str函数是他自己类的函数,而不是实际的std::string c_str。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-05-25
        • 2019-09-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-05-03
        相关资源
        最近更新 更多