Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Error std::bad_array_new_length in a template

I was trying to define my own class template Array<T> to practice the usage of templates.
The code I produced builds properly, but when executed it gives the following error

terminate called after throwing an instance of 'std::bad_array_new_length'
  what():  std::bad_array_new_length

I think I have found a solution to the problem, but I would be interested to see if there was an underlying error in the previous code and if so, which one.

This is the code I previously wrote:

#include <iostream>

class Empty{
private:
    char error;
public:
    Empty(char e) : error(e) { std::cout << "Azione non disponibile, lista vuota" << std::endl;}
};

template <class T>
class Array;

template <class T>
std::ostream& operator<<(std::ostream&,const Array<T>&);

template <class T>
class Array{
    friend std::ostream& operator<< <T> (std::ostream&,const Array<T>&);
private:
    T* arr;
    unsigned int size;
    unsigned int capacity;

    static T* copia(T* a, unsigned int s, unsigned int c){
        if(c > 0) {
            T* app = new T[c];
            for (int i = 0; i<s; ++i) {
                app[i] = a[i];
            }
            return app;
        }else return nullptr;
    }

public:
    Array(int k = 0, const T& t = T()) : size(k > 0 ? k : 0), capacity(size), arr(k > 0 ? new T[size] : nullptr){
        for (int i = 0; i < k ; ++i) arr[i] = t;
    }

    Array(const Array& a) : size(a.size), capacity(a.capacity), arr(copia(a.arr,a.size,a.capacity)){}

    Array& operator=(const Array& a){
        if(this != &a){
            delete[] arr;
            capacity = size = a.size;
            arr = copia(a.arr,a.size,a.capacity);
        }
        return *this;
    }

    ~Array(){delete[] arr;}

    void pushBack(const T& t) {
        if(size == capacity){
            capacity > 0 ? capacity *= 2 : capacity = 1;
            T* app = copia(arr,size, capacity);
            delete[] arr;
            arr = app;
        }
        ++size;
        arr[size-1] = t;
    }

    T popBack() {
        if (size != 0) {
            T temp = arr[size - 1];
            --size;
            return temp;
        } else throw Empty('e');
    }

};

template <class T>
std::ostream& operator<<(std::ostream& os ,const Array<T>& a){
    for (int i = 0; i < a.size; ++i) {
        os << a.arr[i] << ' ';
    }
    std::cout << std::endl;
    return os;
}

int main(){

    Array<int> a(5,5),e;
    std::cout << a << std::endl;

    a.pushBack(16);
    a.pushBack(17);
    a.pushBack(18);

    std::cout << a << std::endl;

    return 0;
}

If I run this code without the a.pushBack(x) function call, it works.
As soon as I insert even one function call, I get that error in the output.

While debugging, I realized that the line where I had written T* arr was not the correct one.
Knowing that the constructor follows the order of initialization of its own sub-objects, the first element to be constructed is the pointer. Since I'm trying to create a vector of elements of T with dimension size, rightly gives me the error, as I have not yet initialized the integer size.
So I solved it by swapping the lines.

template <class T>
class Array{
    friend std::ostream& operator<< <T> (std::ostream&,const Array<T>&);
private:
    unsigned int size;
    unsigned int capacity;
    T* arr;
    ...
};

But at this point I wonder: why, if I don't make the function call, I don't get the same error, knowing that size at the time of construction is undefined?

Logically, the problem should also occur in that case, but everything seems to work:

enter image description here

PS: Don't count on the fact that I didn't handle the exception being thrown, the code is not yet fully complete, but for the moment I was keen to at least implement the Rule of Three.

like image 715
LukeTheWolf Avatar asked Oct 30 '25 10:10

LukeTheWolf


1 Answers

Compiling with GCC 9.1.0 in jdoodle.com, I consistently got a bad_alloc runtime exception with your original code.

I added a new constructor with a different signature so I could see what value of size it was using to allocate the array Note: Even the existence of this new ctor prevented the bad_alloc error, whether it was called or not.

Array(char c, int k = 0, const T& t = T()) : 
    size(k > 0 ? k : 0), 
    capacity(size), 
    arr(DebugInit(size)){
    for (int i = 0; i < k ; ++i) arr[i] = t;
}

T* DebugInit( unsigned long size_init )
{
    std::cout << "DebugInit size_init=" << size_init << " cap=" << capacity << std::endl;
    return size_init > 0 ? new T[size_init] : nullptr;
}

The results seemed to be random when using this new ctor. size_init varied each time, which is consistent with using a member field which has not yet been initialised.

In the original code, it may be that size happened to consistently have 0 in it. By adding more code, even if it was never called, the compiled version would then access a random but now non-zero value for size.

It seems like classic "undefined behaviour". If you use size before it is initialised, there are no guarantees about what will be in it. If you're lucky, size will consistently return 0 before it is initialised and you'll get an allocation error. But a small change to the code may start returning random values for size. If the uninitialised size has a much larger value than the one you intended, you won't see a problem until later, or maybe never.

After a bit more playing around with the code, the std::bad_alloc exception came back! So, definitely no guarantees!

like image 147
John D Avatar answered Nov 01 '25 01:11

John D



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!