我编写了一个函数,它接受一个字符串并返回一个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?
}
答案 0 :(得分:3)
要理解为什么会发生这种情况,您需要了解C ++的几个内在属性。
char* moo()
{
char* a = new char[20];
strcpy(a, "hello");
delete[] a;
return a;
}
请注意,即使我刚刚删除了a
,我也可以返回指向它的指针。主叫方将接收该指针,并且不知道它指向释放的内存。此外,如果您立即打印返回值的值,您很可能会看到&#34; hello&#34;,因为delete
通常不会将内存清零,而是释放。
std::string
是char*
的包装器,它隐藏了一个非常好的界面背后的所有分配和解除分配,因此您不需要关心内存管理。 std::string
的构造函数及其上的所有操作都会分配或重新分配数组,析构函数会释放它。
当您按值传递某个函数时(就像在行encodeUsername
中的username = "@" + username + "@FIN"
函数中那样),它会创建一个新对象,其中包含您传递的内容的副本,一旦功能结束,它将被销毁。因此,在这种情况下,只要encodeUsername
返回,username
就会被销毁,因为它是按值传递的,并且包含在函数的范围内。由于对象被销毁,因此会调用其析构函数,并在此时释放该字符串。通过调用c_str()
检索到的原始数据的指针现在指向不再存在的内容。
在重新分配后立即分配对象时,您很可能会重用刚刚释放的对象的内存。在您的情况下,当您创建一个新字符串tim
时,它会在encodeUsername
返回时刚刚取消分配的同一地址分配内存。
现在,你怎么解决它?
首先,如果你不关心输入字符串(如果你可以覆盖它),你可以通过引用传递它:
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
,那就不会发生。