C++会引发奇怪的行为

C++ ostringstream strange behavior

本文关键字:C++      更新时间:2023-10-16

我最近在c++代码方面遇到了一个非常奇怪的问题。我用极简主义的例子再现了这个案例。我们有一个鸡蛋类:

class Egg
{
private:
    const char* name;
public:
    Egg() {};
    Egg(const char* name) {
        this->name=name;
    }
    const char* getName() {
        return name;
    }
};

我们还有一个篮子类来举办鸡蛋

const int size = 15;
class Basket
{
private:
    int currentSize=0;
    Egg* eggs;
public:
    Basket(){
        eggs=new Egg[size];
    }
    void addEgg(Egg e){
        eggs[currentSize]=e;
        currentSize++;
    }
    void printEggs(){
        for(int i=0; i<currentSize; i++)
        {
            cout<<eggs[i].getName()<<endl;
        }
    }
    ~Basket(){
        delete[] eggs;
    }
};

所以这里有一个和预期一样有效的例子。

 Basket basket;
 Egg egg1("Egg1");
 Egg egg2("Egg2");
 basket.addEgg(egg1);
 basket.addEgg(egg2);
 basket.printEggs();
 //Output: Egg1 Egg2

这是预期的结果,但如果我想根据某个循环变量添加N个生成名称的鸡蛋,我会遇到以下问题。

 Basket basket;
 for(int i = 0; i<2; i++) {
    ostringstream os;
    os<<"Egg"<<i;
    Egg egg(os.str().c_str());
    basket.addEgg(egg);
 }
 basket.printEggs();
 //Output: Egg1 Egg1

如果我将循环条件改变为I<5,我得到了"Egg4 Egg4"。它保存动态Egg数组的所有索引中最后添加的Egg。

在谷歌上搜索后,我发现给Egg中的char*name变量一个固定的大小,并在构造函数中使用strcpy可以解决这个问题。

这是"固定"鸡蛋类。

class Egg
{
private:
     char name[50];
public:
    Egg(){};
    Egg(const char* name)
    {
        strcpy(this->name, name);
    }
    const char* getName()
    {
        return name;
    }
};

现在的问题是为什么?

提前谢谢。

这是整个代码的链接。

让我们仔细看看这个表达式:CCD_ 2。

函数str通过值返回字符串,并通过这种方式使返回的字符串成为临时对象,其生存期仅为表达式结束。表达式结束后,字符串对象将被销毁,不再存在。

传递给构造函数的指针是指向临时字符串对象的内部字符串的指针。一旦字符串对象被破坏,指针就不再有效,使用它将导致未定义的行为

当然,简单的解决方案是,只要您想使用字符串,就使用std::string。更复杂的解决方案是使用数组并在字符串消失之前复制其内容(就像在"固定"Egg类中所做的那样)。但是要注意,使用固定大小数组的"固定"解决方案容易出现缓冲区溢出。

在第一种情况下,您复制指向字符串的指针

在第二种情况下,使用strcpy(),您实际上深度复制字符串


好吧,我没有说太多,让我澄清一下。在第一种情况下,复制指针,该指针指向使用ostringstream创建的字符串。当超出范围时会发生什么?

未定义的行为

os.str()是类型为std::string匿名临时,一旦该匿名临时超出范围(在语句末尾执行),访问.c_str()指向的内存时的行为为未定义。您的第二种情况是有效的,因为strcpy(this->name, name);正在临时超出范围之前获取.c_str()所指向的数据的副本。但代码仍然很脆弱:固定大小的字符缓冲区很容易被溢出。(一个简单的修复方法是使用strncpy)。

但要正确修复,请使用C++标准库:将std::string用作name的类型,将const std::string&用作getName的返回类型,并使用类似std::list<Egg>容器将鸡蛋放在篮子中。

Egg构造函数中不复制字符串,只复制一个指针,即字符串的起始地址。

事情发生了,你的鸵鸟的所有实例都在同一个地方分配缓冲区,一次又一次。在构造os.str().c_str()0循环和输出打印for循环之间,缓冲区没有被覆盖。

这就是为什么最终所有的Egg都有指向同一位置的name指针,并且该位置包含构建的姓氏。

相关文章:
  • 没有找到相关文章