3

In libgmp I see typedef __mpz_struct mpz_t[1]; what does [1] mean here?

Does it mean typedef _mpz_struct* mpz_t?

1 Answer 1

3

It is not the same as a pointer...

It is defining a type named “mpz_t” which is itself an array of __mpz_structs, where the array is exactly one element long.


A lot of times, you can figure this kind of stuff out by simply removing the typedef and see what kind of variable you would be creating. In this case:

__mpz_struct mpz_t[1];

That there is an array of __mpz_structs exactly one element long, named “mpz_t”.

Prefix that typedef and now instead of declaring a variable name you are declaring a type name.



...but it effectively is a pass-by-reference trick

The object of our interest is part of The GNU MP Bignum Library, which defines it as such for a very specific purpose ...but first we need to cover some vocabulary, alas.

By default in C (and C++), function arguments pass by value. In C++, you can also pass by reference, meaning that the formal argument name is treated as an alias for the actual argument value.

  • A direct reference is an alias for some value. (Not necessarily a name, just some kind of direct, bird-in-the-hand access to that value.)

  • An indirect reference is an object that can be used to obtain a direct reference — in other words, a pointer. We dereference a pointer (remove its indirection) to obtain a direct reference.

int   x = 12;  // `x` is a direct reference to an integer object with value 12
int & y = x;   // `y` is a direct reference to that same integer object
int * z = &x;  // `z` is an INdirect reference (pointer) to that same integer object
               // `*z` is a direct reference to that same integer object

But C doesn’t have a direct reference type.
So, obviously, that y thing doesn’t work in C.
But *z does.

The GNU MP library uses a sneaky trick to automagically get pass-by-reference behavior in C.

In C (and in C++) the outermost (or first) dimension of any array automatically decays to a pointer in most contexts, including passing as argument to a function.

What that means is that passing an mpz_t to a function is the same as passing an (indirect) reference to it, an __mpz_struct *.

For example:

mpz_t x;       // HERE is my bignum integer. NOT A POINTER TO ONE.
some_fn( x );  // Pass an (indirect) reference to `x` as argument.

If mpz_t weren’t defined as it is, we would have to explicitly annotate that we want to pass a reference every time we called a function:

struct my_struct_type x;
some_fn( &x );

Wut? Why!??

GNU MP needs you to pass your bignums by reference in order to avoid splicing. See if you can identify the problem with the following code snippet:

typedef struct String { char * s; } String;

void set_string_value( String string, const char * s )  // A
{
  string.s = strdup( s );                               // B
}

char * get_string_value( String string )                // C
{
  return string.s;                                      // D
}

String greeting;
set_string_value( greeting, "Hello world!" );           // E
printf( "%s\n", get_string_value( greeting ) );         // F

If you observe that line B has got a memory leak then you got it! The argument string is a local copy of the caller’s greeting. Modifying it has absolutely no effect on the caller, and when the function terminates its string goes out of scope and is destroyed.

By the time we finish with line E the greeting object has never been modified, and attempting to print something will likely cause an access violation/segmentation fault/whatever Undefined Behavior occurs when printf() tries to follow that uninitialized character pointer in greeting.

The important point is that set_string_value() never modified greeting.

So, how do we fix it? That’s right, require line A to take an (indirect) reference:

void set_string_value( String * string, const char * s )  // A
{
  string->s = strdup( s );                                // B
}
    
String greeting;
set_string_value( &greeting, "Hello world!" );            // E
printf( "%s\n", get_string_value( greeting ) );

Now we can see how GNU MP is sneaky. Let’s rewrite that String thing again:

struct StringStruct { char * s; };
typedef struct StringStruct String[1];

void set_string_value( String string, const char * s )  // A
{
  string->s = strdup( s );                              // B
}

char * get_string_value( String string )                // C
{
  return string->s;                                     // D
}

String greeting;
set_string_value( greeting, "Hello world!" );           // E
printf( "%s\n", get_string_value( greeting ) );         // F

Substituting the type name on line A we see that:

void set_string_value( String string, const char * s )
void set_string_value( StringStruct[1] string, cosst char * s )
void set_string_value( StringStruct string[1], const char * s )
void set_string_value( StringStruct string[], const char * s )
void set_string_value( StringStruct * string, const char * s )

Substituting type for the greeting variable:

String greeting;
StringStruct[1] greeting;
StringStruct greeting[1];
// `greeting` is an array with one element of type StringStruct

And, with array-to-pointer decay, calling the function causes it to get a pointer to greeting, AKA an indirect reference.


dumb tricks...

A lot of older libraries use tricks like this. When dealing with structured types, you will often see things like:

Quux * x = CreateQuux(...);
DoSomeQuuxing( x );
FreeQuux( x );

It has been a while since I touched GNU MP directly (I tend to use Boost.Multiprecision, which handles all the back-end details for me) so I missed the purpose discussed here.

Before flexible array members existed in C (literally ages ago — they were introduced in C99) people would use the hack by declaring a single-element array and simply malloc() more elements as needed. That was, alas, my first (and incorrect) knee-jerk reaction to it.

In today’s modern world you might say that doing stuff like this is dumb. But it is what it is, it is how C works, and a lot of legacy stuff exists that use things like this.

Who knows what GNU MP would look like if it were designed today? Maybe the same.

Sign up to request clarification or add additional context in comments.

3 Comments

why would someone define a type of 1 element array?
One possible reason is to prevent silent accidental copies in function calls. With void my_func(mpz_t a); and mpz_t b; my_func(b);, b is passed by reference (pointer), rather than copied.
Re “Because when the object is allocated, more than one __mpz_struct is allocated in a dynamic array”: No, GMP types are implemented as one-element arrays so that using them as parameters is effectively pass-by-reference.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.