C++ 中未初始化的默认构造函数:munmap_chunk():无效指针

Uninitialized default constructor in c++: munmap_chunk(): invalid pointer

有这个代码:

#include <iostream>
#include <iterator>
#include <initializer_list>
#include <algorithm>

class Foo {
public:
    Foo() = default;
    explicit Foo(size_t size) :size(size){
        ar = new double[size];
    }
    Foo(std::initializer_list<double> initList): Foo(initList.size()){
        std::copy(initList.begin(), initList.end(), ar);
    }
    Foo(double *values, size_t size):size(size), ar(values){}
    Foo(const Foo &rhs): Foo(rhs.size){
        std::copy(rhs.ar, rhs.ar+size, ar);
    }
    ~Foo(){delete[] ar;}
    Foo &operator=(Foo rhs){
        swap(*this, rhs);
        return *this;
    }

    void print(){
        std::copy(ar, ar+size, std::ostream_iterator<double>(std::cout, " "));
        std::cout << std::endl;
    }

private:
    size_t size;
    double *ar;

    static void swap(Foo &f, Foo &s){
        std::swap(f.size, s.size);
        std::swap(f.ar, s.ar);
    }
};

int main() {
    using namespace std;

    size_t size = 100;
    auto *values = new double[size];
    for(int i = 0; i<100; i++){
        double fraction = ((10+i) % 10) / 10.0;
        values[i] = i + fraction;
    }

    Foo f(values, size);
    // Foo g; //IF THIS IS NOT BRACED-INITIALIZED, I GOT munmap_chunk(): invalid pointer
    Foo g{};
    g = f;
    g.print();
}

编程 运行 和出现错误之间的唯一区别是我是否用大括号初始化 Foo g 。为什么那么重要。我知道大括号将 value-初始化 class,这意味着 int *ar 将是 nullptr。如果它不是大括号初始化的,那么 int *arindeterminate。但是,这是什么意思?指针怎么可能不确定?和nullptr一样吗?当指针不确定时,为什么程序会中断?

If it is not brace-initialized, then the int *ar is indeterminate. But what does that mean? How could be pointer indeterminate?

因为您没有为指针分配 任何 值,甚至 nullptr。所以它的值将由已经存储在指针占用的内存位置中的任何随机字节组成。

当使用 = default 声明默认构造函数时,这仅意味着编译器将隐式生成一个构造函数,该构造函数将为您默认初始化每个 class 成员。任何 class 类型的成员都将调用其默认构造函数,并且任何非 class 类型的成员都将分配其默认值 如果这样的值是显式的指定,否则它根本不会被赋值。后者是你的情况。

Is it the same as nullptr?

没有

And why does the program break, when the pointer is indeterminate?

因为指针没有指向有效的内存,所以任何取消引用指针以访问指向的内存的尝试都将失败。包括你的析构函数,它无条件地在指针上调用 delete[],如果指针设置为 nullptr 或指向 有效 new[] 的记忆。

在您的情况下,您应该为非class 类型的成员添加默认值,例如:

private:
    size_t size = 0;
    double *ar = nullptr;

那样的话,如果any构造函数没有显式地给它们赋值,编译器仍然会为它们赋默认值。在这种情况下,Foo() = default; 将生成一个隐式默认构造函数,大致相当于:

Foo() : size(0), ar(nullptr) {}


您还缺少移动构造函数。您现有的 operator= 赋值运算符足以充当复制赋值运算符,但添加移动构造函数将允许它也充当足够的移动赋值运算符,例如:

Foo(Foo &&rhs): size(rhs.size), ar(rhs.ar){
    rhs.ar = nullptr;
    rhs.size = 0;
}

或者:

Foo(Foo &&rhs){
    size = std::exchange(rhs.size, 0);
    ar = std::exchange(rhs.ar, nullptr);
}

或者:

Foo(Foo &&rhs): Foo(){
    swap(*this, rhs);
}

此外,您的 Foo(double*, size_t) 构造函数已损坏。它是 获取传入的 double* 指针的所有权 ,不保证 new[] 是这样的,因此析构函数可以安全地 delete[] 它。

这个构造函数需要分配它自己的 double[] 数组并且 将源值 复制到其中,就像 Foo(std::initializer_list) 构造函数所做的那样,例如:

Foo(const double *values, size_t size): Foo(size) {
    std::copy(values, values+size, ar);
}

综上所述,一个更好更安全的设计是用 std::vector 代替您的手动 double[] 数组,并让它处理所有内存管理和 copy/move 为您操作,例如:

#include <vector>

class Foo {
public:
    Foo() = default;
    explicit Foo(size_t size) : ar(size){}
    Foo(std::initializer_list<double> initList) : ar(initList){}
    Foo(const double *values, size_t size) : ar(values, values+size){}

    // compiler-generated copy/move constructors, copy/move assignment operators,
    // and destructor will suffice, so no need to declare them explicitly...

    void print() const {
        std::copy(ar.cbegin(), ar.cend(), std::ostream_iterator<double>(std::cout, " "));
        std::cout << std::endl;
    }

private:
    std::vector<double> ar;
};