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 withm_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 innerdata_array<int>
should be initialized withm_size=0
since this is what the empty constructor does. But the warning does not appear when initializing an object of typedata_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