室友给出的代码如下:


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的优点很明显:节约内存空间和效率提升