GCC with LTO: Warning: argument 1 value ‘18...615’ (SIZE_MAX) exceeds maximum object size

580 views Asked by At

I learnt recently about Link Time Optimizations in a C++ Weekly episode. I thought using LTO in my code would be a good idea, so I decided to use the flags -flto and -fno-fat-lto-objects. Unfortunately I started getting this warning

data_array.hpp:29:50: warning: argument 1 value ‘18446744073709551615’ exceeds maximum object size 9223372036854775807 [-Walloc-size-larger-than=]
   29 |                 m_data = m_size == 0 ? nullptr : new T[m_size];
      |                                                  ^
data_array.hpp: In function ‘main’:
/usr/include/c++/11/new:128:26: note: in a call to allocation function ‘operator new []’ declared here
  128 | _GLIBCXX_NODISCARD void* operator new[](std::size_t) _GLIBCXX_THROW (std::bad_alloc)
      |                          ^

I use g++ (version g++ (Ubuntu 11.1.0-1ubuntu1~20.04) 11.1.0) on Ubuntu 20.04.

This warning only happens with -flto, and only for GCC, not Clang. 18446744073709551615 is SIZE_MAX, -1ULL, which is strange. The execution of the program after compiling with -fsanitize=undefined comes out clean.

The actual code I'm working is here, but, it being too large, I prepared a working example (see below) for this post that recreates the original context.

Question

  • Can someone help me understand why this warning happens? I think, when the code is merged, the compiler thinks that new is called with an uninitialized value. But this is not true: the array's memory is always allocated with m_size initialized, whether it is exactly 0 (ensured by the empty constructor) or some other value provided by the user, which should be initialized (as shown in the code below).

  • The warning only arises when initializing an object of type data_array<data_array<int>>. The inner data_array<int> should be initialized with m_size=0 since this is what the empty constructor does. But the warning does not appear when initializing an object of type data_array<int>. Why?

  • When I remove the inheritance structure (however simple it might be here), the warning does not arise. Why?

  • Can someone tell me if this will ever lead to undefined behaviour even when m_size is actually initialized? I usually strive to code in a way that no warnings are ever issued; should I just ignore this warning, or add a flag that simply silences it? If not, how do I fix it?

Code to reproduce

First, the class that contains the line in the warning is this custom implementation of a C array. (Edit: I added, after a user pointed this out, the missing copy and move constructors).

#pragma once
#include <algorithm>

template <typename T>
struct data_array {
public:
    data_array() noexcept = default;
    data_array(const std::size_t n) noexcept { m_size = n; alloc_data(); }
    data_array(const data_array& d) noexcept : data_array(d.m_size) {
        if (m_size > 0) {
            std::copy(d.begin(), d.end(), &m_data[0]);
        }
    }
    data_array& operator= (const data_array& d) noexcept {
        if (m_size != d.m_size) {
            delete[] m_data;
            m_size = d.m_size;
            alloc_data();
        }
        if (m_size > 0) {
            std::copy(d.begin(), d.end(), &m_data[0]);
        }
        return *this;
    }
    data_array(data_array&& d) noexcept {
        m_data = d.m_data;
        m_size = d.m_size;
        d.m_data = nullptr;
        d.m_size = 0;
    }
    data_array& operator= (data_array&& d) noexcept {
        delete[] m_data;
        m_data = d.m_data;
        m_size = d.m_size;
        d.m_data = nullptr;
        d.m_size = 0;
        return *this;
    }
    ~data_array() noexcept { clear(); }
    void clear() noexcept {
        delete[] m_data;
        m_size = 0;
        m_data = nullptr;
    }
    void resize(std::size_t new_size) noexcept {
        if (new_size != m_size or m_data == nullptr) {
            delete[] m_data;
            m_size = new_size;
            alloc_data();
        }
    }
    std::size_t size() const noexcept { return m_size; }
    T& operator[] (const std::size_t i) noexcept { return m_data[i]; }
    const T& operator[] (const std::size_t i) const noexcept { return m_data[i]; }

private:
    void alloc_data() noexcept {
        m_data = m_size == 0 ? nullptr : new T[m_size];
    }
protected:
    T *m_data = nullptr;
    std::size_t m_size = 0;
};

(I compressed the code a little, I don't usually code like this). The file that contains the main is the following:

#include <iostream>

#include "data_array.hpp"

class complicated_object_base {
public:
    std::size_t get_size1() const noexcept { return m_vec1.size(); }
    void set_size1(std::size_t size) noexcept { m_vec1.resize(size); }
private:
    std::vector<std::size_t> m_vec1;
};

class complicated_object : public complicated_object_base {
public:
    std::size_t get_size2() const noexcept { return m_vec2.size(); }
    void set_size2(std::size_t size) noexcept { m_vec2.resize(size); }
private:
    std::vector<std::size_t> m_vec2;
};

class my_class {
public:
    my_class(const complicated_object& co) noexcept
        : m_co(co),
          m_data(m_co.get_size1()),
          m_data2(m_co.get_size1())
    { }
    ~my_class() noexcept { }
    void initialize() noexcept {
        for (std::size_t i = 0; i < m_data.size(); ++i) {
            m_data2[i] = i;
            m_data[i].resize(m_co.get_size2());
            for (std::size_t j = 0; j < m_data[i].size(); ++j) {
                m_data[i][j] = 3;
            }
        }
    }
    std::size_t get_sum() const noexcept {
        std::size_t S = 0;
        for (std::size_t i = 0; i < m_data.size(); ++i) {
            S += m_data2[i];
            for (std::size_t j = 0; j < m_data[i].size(); ++j) {
                S += m_data[i][j];
            }
        }
        return S;
    }
private:
    const complicated_object& m_co;
    data_array<data_array<std::size_t>> m_data;
    data_array<std::size_t> m_data2;
};

int main() {
    complicated_object co;
    co.set_size1(100);
    co.set_size2(234);
    my_class mc(co);
    mc.initialize();
    std::cout << mc.get_sum() << '\n';
}

Makefile

CXXFLAGS = -pipe -std=c++17 -fPIC -fopenmp -flto -fno-fat-lto-objects -O3 -Wall -Wextra -Wshadow -Wnon-virtual-dtor -Wold-style-cast -Wcast-align -Wunused -Woverloaded-virtual -Wpedantic -Wconversion -Wsign-conversion -Wnull-dereference -Wdouble-promotion -Wformat=2 -Wduplicated-cond -Wduplicated-branches -Wlogical-op -Wuseless-cast -Wrestrict -UDEBUG -DNDEBUG -fstrict-aliasing -D_REENTRANT -fPIC -fsanitize=undefined
LFLAGS = -fPIC -O3 -flto -fno-fat-lto-objects -DNDEBUG -UDEBUG -Wl,-O3 -fsanitize=undefined
main: main.o
    g++ $(LFLAGS) -o main main.o
main.o: main.cpp data_array.hpp
    g++ $(CXXFLAGS) -c main.cpp
clean:
    rm -f main *.o
0

There are 0 answers