In libgmp I see typedef __mpz_struct mpz_t[1]; what does [1] mean here?
Does it mean typedef _mpz_struct* mpz_t?
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.
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 );
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.
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.
void my_func(mpz_t a); and mpz_t b; my_func(b);, b is passed by reference (pointer), rather than copied.__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.