【问题标题】:Why does this work: returning C string literal from std::string function and calling c_str()为什么这样做:从 std::string 函数返回 C 字符串文字并调用 c_str()
【发布时间】:2014-01-02 12:38:37
【问题描述】:

我们最近在大学举办了一场讲座,教授告诉我们在使用不同语言进行编程时需要注意的不同事项。 以下是 C++ 中的示例:

std::string myFunction()
{
    return "it's me!!";
}

int main(int argc, const char * argv[])
{
    const char* tempString = myFunction().c_str();

    char myNewString[100] = "Who is it?? - ";
    strcat(myNewString, tempString);
    printf("The string: %s", myNewString);

    return 0;
}

这会失败的原因是return "it's me!!" 使用 char[] 隐式调用 std::string 构造函数。该字符串从函数返回,函数c_str()返回指向std::string中数据的指针。

由于函数返回的字符串在任何地方都没有被引用,它应该立即被释放。这就是理论。

但是,让这段代码运行没有问题。 很想听听你的想法。 谢谢!

【问题讨论】:

  • 它不是“没有问题地工作”,它只是假装工作。这是未定义的行为,所以任何事情都可能发生。
  • 内存分配不同,但这个答案仍然适用:stackoverflow.com/a/6445794/13005
  • @SteveJessop +1,我只是在寻找相同的链接来发布它。
  • @MarounMaroun 说来话长:D 它指的是碳酸水中的碳酸,这与虹吸管有关。 (我第一次用这个名字是匈牙利最受欢迎的iPhone博客Szifon,它的读音很像“iPhone”,所以这是一种双关语。)
  • 要获得奖励积分,请询问您的教授为什么不写strcat(myNewString, yFunction().c_str());。 (提示:临时对象一直存在到完整表达式的末尾,所以尽管这种 kind of 看起来相同,但 100% 是明确定义的)。

标签: c++


【解决方案1】:

您的分析是正确的。你所拥有的是未定义的行为。这意味着几乎任何事情都可能发生。在您的情况下,用于字符串的内存虽然已取消分配,但在您访问它时仍保留原始内容。这经常发生,因为操作系统没有清除取消分配的内存。它只是将其标记为可供将来使用。这不是 C++ 语言必须处理的事情:它实际上是一个操作系统实现细节。就 C++ 而言,包罗万象的“未定义行为”适用。

【讨论】:

  • 请注意,在这种情况下,字符串的内容很可能在堆栈上,因为它很短。所以你不应该得到段错误,最多垃圾。
  • This often happens because the OS does not clear out de-allocated memory. 不正确。它不是操作系统,通常是 malloc 实现,它可以是 jemalloc 之类的第 3 方,甚至可以是自定义的。
【解决方案2】:

我猜想释放并不意味着内存清理或归零。显然,这可能会在其他情况下导致段错误。

【讨论】:

    【解决方案3】:

    我认为原因是堆栈内存没有被重写,所以它可以得到原始数据。我创建了一个测试函数并在 strcat 之前调用它。

    std::string myFunction()
    {
        return "it's me!!";
    }
    
    
    void test()
    {
        std::string str = "this is my class";
        std::string hi = "hahahahahaha";
    
        return;
    }
    
    int main(int argc, const char * argv[])
    {
        const char* tempString = myFunction().c_str();
    
    
        test();
        char myNewString[100] = "Who is it?? - ";
        strcat(myNewString, tempString);
        printf("The string: %s\n", myNewString);
    
        return 0;
    }
    

    并得到结果:

    The string: Who is it?? - hahahahahaha
    

    这证明了我的想法。

    【讨论】:

      【解决方案4】:

      正如其他人所提到的,根据 C++ 标准,这是未定义的行为。

      这种“工作”的原因是因为内存已被归还给堆管理器,堆管理器保留它以供以后重用。内存归还给操作系统,因此仍属于该进程。这就是为什么访问释放的内存不会导致分段错误的原因。然而,问题仍然存在,现在您的程序的两个部分(您的代码和堆管理器或新所有者)正在访问他们认为唯一属于他们的内存。这迟早会毁掉一切。

      【讨论】:

        【解决方案5】:

        字符串被释放的事实并不一定意味着内存不再可访问。只要你不做任何可能覆盖它的事情,内存仍然可以使用。

        【讨论】:

        • 字符串的析构函数释放了它的指针,tempString 仍然指向它;取消引用 tempString 会导致 UB。
        • 只要你……和 std::string 的析构函数、堆管理器、操作系统内存管理器或其他线程什么都不做……
        【解决方案6】:

        如上所述 - 这是不可预测的行为。它对我不起作用(在调试配置中)。 std::string 析构函数在分配给 tempString 后立即调用 - 当使用临时字符串对象的表达式完成时。 让 tempString 指向已释放的内存(在您的情况下,它仍然包含“是我!!”文字)。

        【讨论】:

          【解决方案7】:

          你不能通过偶然得到结果来断定没有问题。

          还有其他方法可以检测“问题”:

          • 静态分析。
          • Valgrind 会捕捉到错误,向您显示违规操作(尝试通过 strcat 从释放区域复制)和导致释放的释放。

          Invalid read of size 1

             at 0x40265BD: strcat (mc_replace_strmem.c:262)
             by 0x80A5BDB: main() (valgrind_sample_for_so.cpp:20)
             [...]
          Address 0x5be236d is 13 bytes inside a block of size 55 free'd
             at 0x4024B46: operator delete(void*) (vg_replace_malloc.c:480)
             by 0x563E6BC: std::string::_Rep::_M_destroy(std::allocator<char> const&) (in /usr/lib/libstdc++.so.6.0.13)
             by 0x80A5C18: main() (basic_string.h:236)
             [...]
          

          • 一种真正的方法是证明程序正确。但对于过程语言来说确实很难,而 C++ 则更难。

          【讨论】:

            【解决方案8】:

            实际上,字符串字面量具有静态存储持续时间。它们被打包在可执行文件本身中。它们不在堆栈上,也不动态分配。在通常的情况下,这将指向无效的内存并且是未定义的行为是正确的,但是对于字符串,内存是静态存储的,所以它总是有效的。

            【讨论】:

            • 重点是myFunction() 返回一个std::string。所以const char* tempString = myFunction().c_str(); 指向一个临时的std::string 的内部数据。一旦临时死掉,指针就会“悬空”。
            • 好吧,很公平,我错过了只有一个临时的用于获取缓冲区。我仍然相当确定,如果您使用字符串文字来初始化字符串,c_str() 指针将指向静态 char 数组,但是这取决于实现,不应依赖这种行为。跨度>
            • 并非如此。 std::string 拥有自己用于初始化它的任何字符串的副本。当然,编译器只需要遵循“as-if”规则,所以你描述的可能可能是一种优化。
            • std::string 无法识别指向字符串文字的指针并采取相应的 AFAIK 操作,即使使用编译器扩展也是如此。即使可以,它也会添加另一个特殊情况(以及因此分支和代码),因为它不会尝试在最后释放存储,这可能会使性能总体更差
            • 实际上,可能已经有一个分支尝试不删除字符串,但该分支用于小字符串优化。这不是唯一的相似之处:两种类型的字符串都可以被内存复制。所以一位应该足够了,这可能可以从长度字段的顶部被抢走。 (这很快就被测试为符号 bi)
            【解决方案9】:

            除非我遗漏了什么,否则我认为这是一个范围问题。 myFunction() 返回一个 std::string。字符串对象不直接分配给变量。但它一直在范围内,直到 main() 结束。因此,tempString 将指向内存中完全有效且可用的空间,直到 main() 代码块结束,此时 tempString 也将超出范围。

            【讨论】:

            • 我不认为这是正确的。 std::string 一直存在到分号。然后它被释放。来自c_str 的分配指针指向已释放对象的地址。内存还没有被覆盖,这就是它似乎工作的原因(正如许多其他人已经写的那样)。
            猜你喜欢
            • 2020-03-26
            • 2018-11-22
            • 2021-11-06
            • 2011-11-04
            • 1970-01-01
            • 2021-06-30
            • 2021-05-12
            相关资源
            最近更新 更多