动态内存分配和输入(流)运算符 C++

Dynamic Memory allocation and input(stream) operator C++

我一直在处理一些学校问题,我在 operator>> 中负责内存分配,但是看到了不同的解决方案,所以我在这里有点困惑。operator>>编译时工作良好并提供良好的输出,但我不明白为什么,这是代码.. (为什么我感到困惑在代码下方)

class Some
    {
    protected:
        char *name;
        int price;
        int anything;
    public:
        Some(const char *name="", const int anything=0, const int price=0)
        {
            this->name=new char[strlen(name)+1];
            strcpy(this->name, name);
            this->anything = anything;
            this->price=price;
        }
        ~Some() { delete [] name; }
        friend istream &operator>>(istream &in, Some &i)
        {
            return in>>i.name>>i.anything>>i.price;
        }
        void print(){
           cout << name << " " << anything << " " << price; 
        }
    };

主要

int main() {
    // your code goes here
    Some n;
    cin >> n;
    n.print();
    return 0;
}

所以我们用 Some n 创建了一个对象,但是构造函数只分配了 1 个字符的内存,如果我错了请纠正我(并设置一些默认值)。之后我们使用 >> operator 输入一个 Some 类型的对象,但是 name 只分配了一个字符,我们想输入多少就输入多少。这里是带有一些输入的编译版本 Compiled Code。我的想法哪里错了?或者不应该这样做。谢谢!!

P.s 我们不允许使用负责分配的库..

缓冲区溢出(这就是您正在做的,当您为单个 char 分配 space 时,但通过调用 std::cin >> i.name 写得远远超出它)是被认为是 C++ 中的未定义行为。这意味着编译器几乎可以做任何事情来响应它,甚至是看起来无关或疯狂的事情。

在实践中,这意味着 有时,您的代码会完美运行,没有任何问题....直到您转移到不同的编译器或在不同的日子进行测试或那天早上喝了一种不同的咖啡,此时密码被打破了。 应该 发生的是您的代码应该由此代码引发分段错误(或者,在 Windows 中,访问冲突),但我的猜测(我想要强调这是一个 guess) 是负责分配 name 的 OS 级别调用正在从内存页面的早期获取内存,并且您写入未分配的内存在内存页面的其余部分找到有效的 space。

显然,您不应该依赖这种行为。根据您的 post,您不能使用像 std::vector<char> 或(明显正确的选择)std::string 这样的自动内存分配方案。这可能是因为您的教授是 教给您糟糕的设计原则的白痴,因为他们是一个老傻瓜,已经离开该行业多年并且没有赶上现代 C++ 的使用方式,并且打算教 试图教你如何进行手动内存分配。 出于某些愚蠢的原因。因此,您需要编写代码来处理此问题。

评论中链接的文档 Paul Mckenzie(还有 here)是一个很好的开始。第3页是相关代码。

或者,如果您的教授 突然精神焕发 改变了主意,则更正后的代码将如下所示:

class Some
{
protected:
    std::string name;
    int price;
    int anything;
public:
    //Passing by value means an optimization opportunity. We can move-construct name
    //instead of copying it.
    Some(std::string name = "", const int anything=0, const int price=0) :
    //We'll use a member-initialization list to initialize everything. Saves space
    //and saves a few CPU cycles as well!
    name(std::move(name)), anything(anything), price(price)
    {}

    //We don't need to declare the destructor anymore, because name's memory is automatically
    //managed!
    //~Some() {}

    //this code doesn't need to change.
    friend std::istream &operator>>(std::istream &in, Some &i)
    {
        return in >> i.name >> i.anything >> i.price;
    }

    //This doesn't technically *need* to change, but we can do better.
    //void print(){
       //cout << name << " " << anything << " " << price; 
    //}

    friend std::ostream & operator<<(std::ostream & out, Some const& i) {
        out << i.name << ' ' << i.anything << ' ' << i.price;
    }
};

你对这个问题的思考是正确的。

问题的解决方法是将字符串的长度写入输出文件,然后写入字符串的字符。

假设您反对:

name = "First Last"
price = 15
anything = 0

写对象时,需要有:

10 First Last 0 15

在文件中。

这为您提供了有关该对象的足够信息,使您可以从文件中读回它。

istream &operator>>(istream &in, Some &i)
{
    size_t len;

    // Read the length of name
    in >> len;

    // Allocate memory for name.
    char* name = new char[len+1];

    // Read the name.
    // Discard the whitespace first.
    in.ignore();
    in.read(name, len);

    // Now read anything and price
    in >> i.anything >> i.price;

    // Release memory held by i.name before using memory allocated
    // in this function
    delete [] i.name;
    i.name = name;

    return in;
}

Some 类型的对象写入文件的函数必须镜像从文件中读回它的函数。

std::ostream& operator<<(std::ostream& out, Some const& i)
{
    out << strlen(i.name) << " ";
    out.write(i.name, strlen(i.name);
    return out << " " << i.anything << " " << i.price        
}