3

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
  • do you get the same warning from the MWE? no warnings here: https://godbolt.org/z/vbn6hhfvE – 463035818_is_not_an_ai Jul 20 '22 at 08:05
  • 1
    @463035818_is_not_a_number It does reproduce. https://godbolt.org/z/9fdvxqn7e – Nimrod Jul 20 '22 at 08:08
  • @463035818_is_not_a_number The warning only arises when using LTO. No warning without it. Also, I couldn't recreate the warning without inheritance in the `complicated_object` class. This motivates the (rather) long "M"WE – Lluís Alemany-Puig Jul 20 '22 at 08:08
  • 1
    @Nimrod Thank you. I changed the compiler to 12.1, and it does not appear. Perhaps 11.1 has a bug? – Lluís Alemany-Puig Jul 20 '22 at 08:09
  • 1
    Default `data_array` copy constructor is wrong BTW (you don't follow rule of [3/5/0](https://en.cppreference.com/w/cpp/language/rule_of_three)) leading to UB with double delete. – Jarod42 Jul 20 '22 at 08:35
  • Interestingly, gcc 11.3 has the error, but it goes away if the ctor is written like `data_array(const std::size_t n) noexcept: m_data{new T[n]}, m_size{n} {}`. does not go away for gcc 11.2 or 11.1 – starball Jul 20 '22 at 08:39
  • It passes a constant evaluation test on both GCC and MSVC (Clang has some unrelated hangup), so it seems unlikely that there is undefined behavior, especially of the kind the warning is claiming: https://godbolt.org/z/cKdWb1Gh1 – user17732522 Jul 20 '22 at 08:41
  • I wonder if this could have anything to do with the fact that `constexpr std::vector` was implemented in gcc starting in gcc 12? but that wouldn't directly explain my earlier comment about 11.3 and direct member initialization-ing the ctor – starball Jul 20 '22 at 08:49
  • Side note: [raw pointers are non-owning](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#r3-a-raw-pointer-a-t-is-non-owning), you should better use a `std::unique_ptr`. You would end up with less code and with safer code. – Werner Henze Jul 20 '22 at 09:41
  • @Jarod42 Yes, you're right. Note, though, that the missing constructors were removed to make the code smaller. – Lluís Alemany-Puig Jul 20 '22 at 14:01
  • 1
    That missing method is used leading to UB which might have been the cause (it is not). And you still keep not relevant methods. I shorter your [MCVE](https://godbolt.org/z/bGPYh3a8r) with a "valid" (missing copy) copy constructor. – Jarod42 Jul 20 '22 at 14:10
  • `18446744073709551615` is `-1ULL` aka `SIZE_MAX`. I'm not sure where that's coming from, though. – Peter Cordes Jul 20 '22 at 14:27
  • @Jarod42: was able to make a test-case that should be valid C++17. It's potentially a compiler bug since clang doesn't produce this warning. Consider reporting it on https://gcc.gnu.org/bugzilla/enter_bug.cgi?product=gcc with a test-case like that, and maybe link this Q&A as well as include your explanation. Especially if you can also build and run it with `-fsanitize=undefined` and it comes out clean. – Peter Cordes Jul 21 '22 at 13:35
  • @PeterCordes I wanted to create the bug report at bugzilla but the web didn't allow me. " User account creation has been restricted." I've already requested the creation of an account for me. But perhaps you or somebody else in this discussion can create the bug report for me. – Lluís Alemany-Puig Jul 22 '22 at 07:51
  • @llualpu - yeah, spammers ruin everything. The GCC devs should get back to you with account creation soon. If I have spare time to report it, I might, but I don't expect to. – Peter Cordes Jul 22 '22 at 16:04

0 Answers0