如何创建一个字符串来改变const char *指向的值?

时间:2015-11-28 03:52:41

标签: c++ string memory-management

我编写了一个函数,它接受一个字符串并返回一个const char *,其中包含该字符串的编码版本。我调用此函数,然后创建一个新字符串。在这样做的时候,我无意中无意中改变了指向我的const char *的值,我认为这是不可能的。

但是,当我不使用自己的函数时,只需将值硬编码到我的const char数组中,当我创建字符串时,该值不会改变。为什么这里有区别,为什么我能够改变const char数组的值呢?

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <iostream>

using namespace std;

// returns "@username@FIN"
const char* encodeUsername(string username)
{
    username = "@" + username + "@FIN";
    return username.c_str();
}

int main(void)
{
    string jack("jack");
    const char* encodedUsername = "@jack@FIN";
    string dummy("hi");
    printf("%s\n", encodedUsername); //outputs "@jack@FIN", as expected.

    string tim("tim");
    const char* encodedUsername2 = encodeUsername(tim);
    string dummy2("hi");
    printf("%s\n", encodedUsername2); //outputs "hi". Why?
}

2 个答案:

答案 0 :(得分:3)

要理解为什么会发生这种情况,您需要了解C ++的几个内在属性。

  1. 在C ++中,指针可以指向已释放的内存区域。这是许多其他语言无法做到的事情,它可以隐藏一些严重的错误。例如,请考虑以下代码:
  2. char* moo()
    {
        char* a = new char[20];
        strcpy(a, "hello");
        delete[] a;
        return a;
    }
    

    请注意,即使我刚刚删除了a,我也可以返回指向它的指针。主叫方将接收该指针,并且不知道它指向释放的内存。此外,如果您立即打印返回值的值,您很可能会看到&#34; hello&#34;,因为delete通常不会将内存清零,而是释放。

      粗略地说,
    1. std::stringchar*的包装器,它隐藏了一个非常好的界面背后的所有分配和解除分配,因此您不需要关心内存管理。 std::string的构造函数及其上的所有操作都会分配或重新分配数组,析构函数会释放它。

    2. 当您按值传递某个函数时(就像在行encodeUsername中的username = "@" + username + "@FIN"函数中那样),它会创建一个新对象,其中包含您传递的内容的副本,一旦功能结束,它将被销毁。因此,在这种情况下,只要encodeUsername返回,username就会被销毁,因为它是按值传递的,并且包含在函数的范围内。由于对象被销毁,因此会调用其析构函数,并在此时释放该字符串。通过调用c_str()检索到的原始数据的指针现在指向不再存在的内容。

    3. 在重新分配后立即分配对象时,您很可能会重用刚刚释放的对象的内存。在您的情况下,当您创建一个新字符串tim时,它会在encodeUsername返回时刚刚取消分配的同一地址分配内存。

    4. 现在,你怎么解决它?

      首先,如果你不关心输入字符串(如果你可以覆盖它),你可以通过引用传递它:

      const char* encodeUsername(string& username)
      

      这将解决它,因为username不是副本,因此它不会在函数末尾被销毁。但是,现在的问题是这个函数会改变你传入的字符串的值,这是非常不合需要的,并且会创建一个不直观的界面。

      其次,你可以在返回之前分配一个新的char数组,然后在调用函数的末尾释放它:

      const char* encodeUsername(string username)
      {
          username = "@" + username + "@FIN";
          return strdup(username.c_str());
      }
      

      然后在main的末尾:

      free(encodedUsername);
      free(encodedUsername2);
      

      (请注意,您必须使用free而不是delete[],因为数组是使用strdup分配的)

      这将起作用,因为我们返回的char数组在返回之前就已在堆上分配,并且未被释放。它的价格是现在调用函数需要释放它,这又是一个不直观的界面。

      最后,正确的解决方案是返回std::string而不是char指针,在这种情况下,std::string将为您处理所有分配和解除分配:

      string encodeUsername(string username)
      {
          username = "@" + username + "@FIN";
          return username;
      }
      

      然后在主要功能中:

      string encodedUsername2 = encodeUsername(tim);
      printf("%s\n", encodedUsername2.c_str());
      

答案 1 :(得分:1)

username返回时encodeUsername的生命周期终止,该函数返回的指针悬空。换句话说,它是Undefined Behavior,在这种情况下,它表现为重用encodeUsername指向的内存,为新创建的字符串返回值。

如果你自己返回std::string,那就不会发生。