[C++]一个关于写时Copy的问题
室友给出的代码如下:
class Person{
public:
void setName(const char* name){
strcpy(this->name,name);
}
const char* getName(){
return this->name;
}
private:
char name[10];
};
int main(){
Person p;
char name[10] = {"matrixA"};
p.setName(name);
cout << p.getName() << endl;//输出"matrixA"
char* tmpName = const_cast<char*>(p.getName());
strcpy(tmpName,"matrixB");
cout << p.getName() << endl;//输出"matrixB"
return 0;
}
看main函数中,最后一次打印p.getName(),理想的情况应该是输出”matrixA”,然而实际输出的是“matrixB”,竟然在类外对类的私有变量进行了write操作!于是室友惊呼:“我发现了C++的一个Bug!”
看到这个问题,我的直觉是字符串的问题,因此写下了下边代码作为对比:
class Person{
public:
void setName(string name){this->name = name;}
string getName(){return this->name;}
private:
string name;
};
int main(){
Person p;
string name = "matrixA";
p.setName(name);
cout << p.getName() << endl;//输出“matrixA”
string tmpName = p.getName();
tmpName = "matrixB";
cout << p.getName() << endl;//输出"matrixA"
return 0;
}
对字符串的声明修改为string之后,看main函数中,最后一次打印p.getName(),实际输出和理想保持一致。阅读代码,给tmpName赋值”matrixB”,并没有对类中的私有变量进行write操作。
为了证实第一段代码确实修改了类中私有变量name,利用GDB调试代码,打印p.name和tmpName变量地址,和预期一样。室友给出的代码Addr(tmpName) == Addr(p.name),而我的代码Addr(tmpName) != Addr(p.name)。
为什么?C++的Bug吗?上代码如下:
string sa = "matrixA";
string sb = sa;
string sc = sb;
sc = "matrixB";
将”matrixB”赋值给sc之前和赋值之后,在不同的机器上(实际上这是不严格的表达)打印Addr(sa),Addr(sb),Addr(sc),并进行比较,结果如下:
1.Mac OS X 10.10.5 && Ubuntu16.04 64bit:给sc赋值前,Addr(sa) != Addr(sb) != Addr(sc);赋值后,Addr(sa) != Addr(sb) != Addr(sc)
2.Windows 7 Code Blocks 16:给sc赋值前,Addr(sa) == Addr(sb) == Addr(sc);赋值后,Addr(sa) == Addr(sb) != Addr(sc)
上述的表达实际上是不严谨的,后经搜索资料。发现问题实际上是与C++的string实现方式有关,eager-copy和copy-on-write。结果1的机器内置库string的实现应该是eager-copy方式,简单地说,每个string有一个独立的内存空间。结果2的机器内置库string的实现应该是copy-on-write方式,简单地说,字符串的内容在未被修改时,拷贝时只是共享指针,共享内存空间。此刻,应该回到代码,这里也有一个坑。
回到室友给出的代码,getName()返回的是const char*类型,main函数中有这样一行代码:
char* tmpName = const_cast<char*>(p.getName());
强制类型转换将const char类型转换为char类型,同时,声明的tmpName同样也是char*类型,这可能就为copy-on-write方式创造了条件。
但是问题是,为什么char*和string在字符串copy时的行为不一致?
虽然,此坑很有可能是由写时copy造成的,但是写时copy的优点很明显:节约内存空间和效率提升。