简体   繁体   中英

Why is stack smashing detected if an empty array[] is initiated in the constructor < 2 inside a class

I am running into this error... 在此处输入图像描述

...with this minimal executable code to reproduce the error and...

#include <iostream>

class arrayClass
{
private:
   int _elements;
   int _array[];

public:
    arrayClass()
        : _elements(32)
    {
        //If i is smaller than 2 the "Stack Smashing Detected" error shows up, why is that?
        //If i is 2 or more, no error appears
        //(f.e. int i = 0 or int i = 1 doesn't work, int i = 2 or higher works - why is that?)
        for(int i = 0; i < _elements; ++i){
            _array[i] = 0;
        }
    }
    int get(int index){
        return _array[index];
    }
};

int main()
{
    arrayClass arr;
    std::cout << arr.get(2) << std::endl;

    return 0;
}

...it doesn't matter if I initiate _elements with the initialization list or at with the attribute itself with fe32 or whatever number.

If I pass the int value to construct the arrayClass(int) in addition to the arrayClass() constructor, the error doesn't shows up.

If I construct the arrayClas(int) with a value alone, it can also just be used with the 2nd slot upwards.

So my question is: Why couldn't I initiate the 1st and 2nd array slot of a default array[] ?

Or the other way around, why is it possible to assign an empty array[] to a class without a value and not fe _array[32] without an error but the one with assigning array[0] = 0; and array[1] = 0; ?

(And yes, I am aware of vectors, I need to use arrays for various reasons)

Because you never allocate memory for the array to begin with, everything is undefined behavior. I don't even know what int _array[] evaluates to without a size specifier. I'll look that up later.

Change your construct code to "new" the array. And have a destructor to delete it.

class arrayClass
{
private:
   int _elements;
   int* _array;

public:
    arrayClass()
        : _elements(32)
    {
        _array = new int[_elements];
        memset(_array, '\0', sizeof(array[0])*_elements);
    }
    int get(int index){
        return _array[index];
    }

    ~arrayClass()
    {
        delete [] _array;
    }
};

Or if you can have a fixed number of elements, explictly size the array when it's declared:

class arrayClass
{
private:
   int _array[32];

public:
    arrayClass()
        : _array() // this will zero-init the array
    {
    }
    int get(int index){
        return _array[index];
    }
};

int _array[]; is a flexible array and isn't allowed in standard C++. It does not allocate memory so when you access any element in the array, you have undefined behavior.

I am aware of vectors, I need to use arrays for various reasons

In reality there are very few valid reasons so I expect the various reasons you mention to be artificial. If you need to pass the data to a function, like void func(int*, size_t elements); , you can still use a std::vector<int> . Just pass its data() and size() as arguments.

In C++ you should typically use a std::vector<int> for cases like this.

Example:

#include <iostream>
#include <vector>

class arrayClass
{
private:
   std::vector<int> _array;

public:
    arrayClass(size_t s = 32)
        : _array(s)
    {}

    size_t size() const {
        return _array.size();
    }

    int get(size_t index) const {
        return _array[index];
    }
};

int main()
{
    arrayClass arr;
    std::cout << arr.get(10) << std::endl;

    return 0;
}

An alternative, if your arrayClass has a fixed number of elements:

#include <array>

class arrayClass
{
private:
   std::array<int, 32> _array;

public:
    arrayClass()
        : _array{}
    {}

    size_t size() const {
        return _array.size();
    }

    int get(size_t index){
        return _array[index];
    }
};

If the extra space a std::vector consumes (usually 4 or 8 bytes) is a real concern, you could make a similar class that only stores the pointer to the allocated memory and the size. It could look like this (but doesn't have the ability to grow/shrink like a vector has):

#include <iostream>

#include <algorithm>
#include <memory>
#include <type_traits>

template<typename T, std::enable_if_t<std::is_default_constructible_v<T>>* = nullptr>
class arrayClass {
public:
    using value_type = T;

    arrayClass(size_t size = 32) :
        _size(size),
        _array(std::make_unique<T[]>(_size))
    {}

    // copy constructor
    arrayClass(const arrayClass& rhs) :
        _size(rhs._size),
        _array(std::make_unique<T[]>(_size))
    {
        static_assert(std::is_copy_assignable_v<T>, "T must be copy assignable");
        std::copy(rhs._array.get(), rhs._array.get() + _size, _array.get());
    }

    arrayClass(arrayClass&&) = default; // move constructor

    // copy assignment operator    
    arrayClass& operator=(const arrayClass& rhs) {
        *this = arrayClass(rhs); // copy construct and move assign
        return *this;
    }

    arrayClass& operator=(arrayClass&&) = default; // move assignment operator

    // accessing element at index
    T& operator[](size_t index) { return _array[index]; }
    const T& operator[](size_t index) const { return _array[index]; }

    // bounds checking access to element
    T& at(size_t idx) {
        if(idx >= _size) 
           throw std::out_of_range(std::to_string(idx) + ">=" + std::to_string(_size));
        return _array[idx];
    }
    const T& at(size_t idx) const {
        if(idx >= _size)
           throw std::out_of_range(std::to_string(idx) + ">=" + std::to_string(_size));
        return _array[idx];
    }

    size_t size() const { return _size; }

    // support for iterating over the elements in the array
    const T* cbegin() const { return _array.get(); }
    const T* cend() const { return _array.get() + _size; }
    const T* begin() const { return cbegin(); }
    const T* end() const { return cend(); }
    T* begin() { return _array.get(); }
    T* end() { return _array.get() + _size; }   

private:
   size_t _size;
   std::unique_ptr<T[]> _array;
};

using intArray = arrayClass<int>;

int main() {
    try {
        intArray arr1(10);
        // the added iterator support makes range-based for-loops possible:
        for(int& v : arr1) {
            static int i=0;
            v = ++i;
        }

        intArray arr2;
        arr2 = arr1; // copy assign
        for(size_t i=0; i < arr2.size(); ++i) {
            std::cout << arr2[i] << '\n';       // access using operator[] works too
        }    

        std::cout << arr2.at(10) << '\n'; // throws out_of_range exception
    }
    catch(const std::out_of_range& ex) {
        std::cerr << ex.what() << '\n';
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM