[Boost-users] [Multiprecision] Runtime assertion failure with clang++3.6.0 but not g++4.9.2

42 views
Skip to first unread message

mbw...@mailbox.org

unread,
Feb 8, 2016, 1:24:41 PM2/8/16
to boost...@lists.boost.org
Dear all,

this is my first post on this mailing list, so hopefully this will all be
by the book.
Executing the following minimal working example, which is supposed to
calculate "3! == 6" via
(1) a template function
(2) a class template

and save the result in a boost::multiprecision::mpz_int, results in
unexpected behavior for clang++3.6.0,
with the version of my boost library being 1.59.0.

The program displays the correct result using g++4.9.2 however:


#include <iostream>
#include <boost/multiprecision/gmp.hpp>

namespace bm = boost::multiprecision;

template <std::size_t N>
bm::mpz_int factorial()
{
return N * factorial<N-1>();
}

template<>
bm::mpz_int factorial<0>()
{
return 1;
}

template <std::size_t N>
struct factorial2
{
static const bm::mpz_int value;
};
template<std::size_t N>
const bm::mpz_int factorial2<N>::value = N * factorial2<N-1>::value;

template<>
struct factorial2<0>
{
static const bm::mpz_int value;
};
const bm::mpz_int factorial2<0>::value = 1;

int
main ()
{
std::cout << factorial<3>() << '\n';
std::cout << factorial2<3>::value << '\n';
return 0;
}

The output for clang, having been compiled via

clang++ -std=c++14 -stdlib=libc++ -DNDEBUG -o main main.cpp -lgmp

will result in:

6
0

However, omitting the NDEBUG flag will trigger an assertion failure:

main: /usr/include/boost/multiprecision/gmp.hpp:1296: const mpz_t
&boost::multiprecision::backends::gmp_int::data() const: Assertion
`m_data[0]._mp_d' failed.
Aborted (core dumped)

Compiling the same program with g++4.9.2 (with or without "-DNDEBUG")
yields the expected result:

6
6

What did I miss? Can this behavior indeed be traced down to
Boost.Multiprecision, or does it have to do with the initialization order
of static const class members? Or something else?
Additionally, both variants work correctly with clang when using a
built-in type like 'int'.

Any help in understanding the problem on hand would be greatly
appreciated!


Best Regards,

mbw
_______________________________________________
Boost-users mailing list
Boost...@lists.boost.org
http://lists.boost.org/mailman/listinfo.cgi/boost-users

John Maddock

unread,
Feb 8, 2016, 2:14:53 PM2/8/16
to boost...@lists.boost.org

> However, omitting the NDEBUG flag will trigger an assertion failure:
>
> main: /usr/include/boost/multiprecision/gmp.hpp:1296: const mpz_t
> &boost::multiprecision::backends::gmp_int::data() const: Assertion
> `m_data[0]._mp_d' failed.
> Aborted (core dumped)
That indicates that an uninitialized variable was used.

> Compiling the same program with g++4.9.2 (with or without "-DNDEBUG")
> yields the expected result:
>
> 6
> 6
>
> What did I miss? Can this behavior indeed be traced down to
> Boost.Multiprecision, or does it have to do with the initialization order
> of static const class members? Or something else?

I would say initialization order.

> Additionally, both variants work correctly with clang when using a
> built-in type like 'int'.

For type int, the values are computed recursively at compile time, for a
user-defined-type the values are computed at runtime and you hit
initialization-order issues.

HTH, John.

Jeff Schwab

unread,
Feb 8, 2016, 4:30:58 PM2/8/16
to boost...@lists.boost.org
Seems to be that Multiprecision relies on static data that haven't (necessarily) been initialized before your own static members.  Works fine if the members are made non-static:

#include <iostream>
#include <boost/multiprecision/gmp.hpp>

namespace bm = boost::multiprecision;

template <std::size_t N>
struct factorial2
{
    const bm::mpz_int value = N * factorial2<N-1>().value;
};

template<>
struct factorial2<0>
{
    const bm::mpz_int value = 1;
};

int main()
{
    std::cout << factorial2<3>().value << '\n';
    return 0;
}

mbw...@mailbox.org

unread,
Feb 8, 2016, 7:44:14 PM2/8/16
to boost...@lists.boost.org
Hello again and thanks for your replies!

Taking the following two statements into account:

> For type int, the values are computed recursively at compile time, for a
> user-defined-type the values are computed at runtime and you hit
> initialization-order issues.

> Seems to be that Multiprecision relies on static data that haven't
> (necessarily) been initialized before your own static members. Works
> fine
> if the members are made non-static:

Does that mean that, since
a) boost::multiprecision::number<gmp_int> is not constexpr enabled, and
b) static initialization can't happen at compile-time for user-defined
types,

that there is no way for me to initialize a variable of this type at
compile-time?
Does b) follow logically from a) ?

I (believe to have) confirmed runtime-initialization by running valgrind,
which showed the presence of dynamic memory allocations (and also jumps
depending on uninitialized variables, which probably corresponds to the
assertion failure). I am mentioning this because initially my little test
program looked somewhat like this:

#include <iostream>
#include <vector>
#include <boost/multiprecision/gmp.hpp>

namespace bm = boost::multiprecision;


template<std::size_t N>
struct factorial
{
static const bm::mpz_int value;
static void add_values(std::vector<bm::mpz_int>& vec)
{
factorial<N-1>::add_values(vec);
vec.push_back(value);
}
};
template<std::size_t N>
const bm::mpz_int factorial<N>::value = N * factorial<N-1>::value;

template<>
struct factorial<std::size_t{0}>
{
static const bm::mpz_int value;
static void add_values(std::vector<bm::mpz_int>& vec)
{
vec.push_back(value);
}
};
const bm::mpz_int factorial<0>::value = 1;


int
main ()
{
std::vector<bm::mpz_int> fac_table{};
factorial<10'000>::add_values(fac_table);
std::cout << "Enter a number between 0 and 10'000 to calculate
factorial:";
std::size_t number;
std::cin >> number;
std::cout << '\n' << number << "! == " << fac_table[number] << '\n';

return 0;
}

This still includes the "unsafe" static initialization of a user-defined
type. However it compiles and runs via

g++ -ansi -std=c++14 -O3 -Wall -Wextra -pedantic -static -save-temps
-ftemplate-depth-50000 -DNDEBUG -o main main.cpp -lgmp -pthread

This was what made me believe in the compile-time computation of the
factorials in the first place, since using this version, valgrind does not
show any dynamic memory allocation at all. On the other hand, I don't
understand what's going on here really, especially since std::vector<> is
supposed to allocate memory at run-time.

Now I'm not sure whether the last example has anything to do with
Boost.Multiprecision anymore, so while it would really make my day if you
could help me better understand this, I will understand if this is not the
right place to ask these questions.

Thanks again for your replies so far, they have been of great help!


Cheers,

Max

John Maddock

unread,
Feb 9, 2016, 5:08:44 AM2/9/16
to boost...@lists.boost.org


On 08/02/2016 18:51, Jeff Schwab wrote:
> Seems to be that Multiprecision relies on static data that haven't
> (necessarily) been initialized before your own static members. Works
> fine if the members are made non-static:
>

There is no static data in mpz_int being relied on here.

John.

John Maddock

unread,
Feb 9, 2016, 5:12:55 AM2/9/16
to boost...@lists.boost.org

> Does that mean that, since
> a) boost::multiprecision::number<gmp_int> is not constexpr enabled, and
> b) static initialization can't happen at compile-time for user-defined
> types,
>
> that there is no way for me to initialize a variable of this type at
> compile-time?

Correct - this is primarily a limitation of GMP which dynamically
allocates memory for each number.

Type cpp_int when used at fixed precision (ie
boost::multiprecision::int128_t etc) has support for constexpr
initialization, but only from literals, there's still no constexpr
arithmetic (that would be really hard!).

HTH, John.

Jeff Schwab

unread,
Feb 9, 2016, 5:35:42 AM2/9/16
to boost...@lists.boost.org
On Tue, Feb 9, 2016 at 5:08 AM, John Maddock <jz.ma...@googlemail.com> wrote:

There is no static data in mpz_int being relied on here.

OK, but what is causing the initialization order issue?  Reordering definitions in the OP's code does not change the error, and the warning is coming from gmp.hpp.

John Maddock

unread,
Feb 9, 2016, 7:56:55 AM2/9/16
to boost...@lists.boost.org
The initialization of:

factorial2<N>::value

depends on

factorial2<N-1>::value

being already initialized, but there is no guaranteed ordering for
initialization of instances of

factorial2<M>::value

As I understand it, the compiler can choose any order it wants -
alphabetical, order of instantiation, random, or "whatever". GCC
chooses an order that accidentally works in this particular case,
whereas clang does not.
Reply all
Reply to author
Forward
0 new messages