Search code examples
c++gmpbignum

How to check if mpz_t number is initialized using GMP?


Using GMP library in c++. I have a function that receives a pointer to a mpz_t number and sets another gmp_z number from that. I need a way to check if *_amount has been initialized before.

void f(mpz_t* _amount)
{
    mpz_t amount;
    mpz_init(amount);

    if(!_amount){
        throw std::bad_alloc();
    }

    mpz_set(amount, *_amount);
}

How can I check if _amount has been initialized previously?


Solution

  • This is bad practice in C - end of story. You don't check that _amount has been initialized. The GMP library is highly optimized, and ensuring an mpz_t object is correctly initialized is the only correct approach. So an mpz_t object might look initialized, but unless mpz_init(_amount) has been used, the fields might be uninitialized garbage.

    It doesn't matter if _amount has already been set. Look at mpz/set.c :

    void
    mpz_set (mpz_ptr w, mpz_srcptr u)
    {
      mp_ptr wp, up;
      mp_size_t usize, size;
    
      usize = SIZ(u);
      size = ABS (usize);
    
      wp = MPZ_REALLOC (w, size);
    
      up = PTR(u);
    
      MPN_COPY (wp, up, size);
      SIZ(w) = usize;
    }
    

    Clearly, if the fields of mpz_t (see: gmp-h.in) are uninitialized, they could be garbage. mpz_set could perform realloc on contiguous memory that has not been allocated. If you're lucky, that's a crash. If not, the program continues in an undefined state.

    It might be useful to think of mpz_t as 'C objects'. They are not valid (and cannot be used correctly) until initialized. I would also stress that when it comes to the requirements of GMP (multiple-precision arithmetic), the 'overhead' of initializing a variable (e.g., to zero) is absolutely negligible.

    So let's assume _amount has been correctly initialized:


    mpz_init(_amount); /* correctly sets mpz_t and assigns (0). */
    
    /* (maybe `_amount` isn't (0)... that's OK too) */
    
    void f (mpz_t *_amount)
    {
        mpz_t amount;
        mpz_init(amount);
    
        /* unless using the GMP C++ interface, there's no definition
         * for the (!) operator. However `mpz_sgn` can be used here: */
    
        if (mpz_sgn(_amount) == 0) /* (_amount == 0 : exit condition?) */
        {
            mpz_clear(_amount); /* free space occupied by (_amount) */
    
            /* NOTE: this is NOT C++ with object unwinding! (amount) will
             * potentially leak resources! */
    
            mpz_clear(amount);
            throw std::bad_alloc {}; /* no leaks; deallocated storage. */
        }
    
        /* since (amount) will need to be deallocated, and we don't want
         * to deal with assignment errors - we simply 'swap' contents. */
    
        mpz_swap(_amount, amount); mpz_clear(amount);
    }
    

    I feel you are mixing paradigms here. The use of throw in what is essentially C code forces you to manually clean up, and mpz_t is not a C++ object that 'constructs' to a default, or frees all resources as it destructs / passes out of scope.

    You might find the GMP C++ interface more appropriate for prototyping your code. mpz_class acts as a C++ class, which initializes when instanced, and calls it's destructor at the end of its scope.

    Right now you are mixing C/C++ incorrectly, and leaving yourself vulnerable to a lot of bugs, some of which may not cause an immediate crash - and these can be the worst to pin down...