I have C code that wants to do something like this:
#include <gmp.h>
extern void g(mpz_t in);
extern mpz_t temp; /* initialized elsewhere */
void f(mpz_t in, int flag) {
mpz_t *arg;
if (flag) {
mpz_mul_ui(temp, in, 2);
arg = &temp;
} else {
/* this bit is the problem */
arg = ∈
}
/* placeholder for more complex code using *arg (readonly) */
g(*arg);
}
The purpose here is to avoid a lot of duplicated code represented here by g(*arg)
– I want it to act either on the original input or on a modified version of it.
But while arg = &temp
compiles fine, arg = &in
does not. I understand that this is because mpz_t
is typedef'd as an array (typedef __mpz_struct mpz_t[1]
), and from Error when attempting to use a type from GNU's GMP library as the yylval type for Bison I get the additional clue "the array decays to a pointer when used as a function argument", but it isn't clear to me what the actual type of that pointer is so that I can do the right thing here. I've tried arg = &in
, arg = in
, arg = &in[0]
, and, in each case, GCC just tells me "warning: initialization from incompatible pointer type".
My system GCC (7.5.0) sadly does not report what the incompatible types are but, trying again with a later version (11.2.0) does tell me warning: assignment to '__mpz_struct (*)[1]' from incompatible pointer type '__mpz_struct **'
, which is great. But I can't guess from that if there's an invocation that will allow the assignment safely without a cast, or if a cast is required what I should be casting – should it be arg = (mpz_t *)&in
? Is there a way to do this without casts?
CodePudding user response:
Your overall assessments of the problems are about right:
Because the
mpz_t
type is defined as an array (even though it has only one element), arguments of that type will decay to pointers (to the first – and only – element).Also, because it is an array type, you cannot make direct assignments to objects of that type (so you couldn't just remove the
*
from the declaration ofarg
, for example).
What you can do, though, is to declare the local arg
variable as a pointer to the underlying type of the array – that is the __mpz_struct
type. So, you would have: __mpz_struct* arg;
.
With this, you must then remove the address-of (&
) operator from the right-hand side of the two arg
assignments in your function: the temp
array will (when used in such an expression) automatically 'decay' into a pointer; and the in
argument has already decayed thusly.
Here's an outline:
#include <gmp.h>
extern void g(mpz_t in);
extern mpz_t temp; /* initialized elsewhere */
void f(mpz_t in, int flag) {
__mpz_struct* arg; // Pointer to the underlying type of an "mpz_t" array
if (flag) {
mpz_mul_ui(temp, in, 2);
arg = temp; // RHS decays into a __mpz_struct* pointer
}
else {
/* problem solved: */
arg = in; // RHS is already a __mpz_struct* pointer
}
/* placeholder for more complex code using arg (readonly) */
g(arg); // Note that the "*" needs to be gone, here!
}
Note also that, in the above code, there is no cast used!
Notes on the use of the mpz_t* arg;
(pointer-to-array) type and casts.
In terms of its actual value, the address of an array is the same as the address of its first element. However, a pointer to an array is not the same type as a pointer to its first element. We can use two methods to convert one to the other: a cast, or an "address of"/indirection operator (depending on the direction of the conversion).
Further, in almost all expressions, the use of an array identifier (without a suffixed [...]
) will be converted to a pointer to its first element. Thus, in the expression, x = temp
(using the temp
from the original code), the RHS will decay to a pointer to temp[0]
. As this is the same address as that of the array itself, we can (safely) cast this to an mpz_t*
type in an assignment to arg
:
arg = (mpz_t*)temp;
But we can avoid a cast, here. One place where an array does not decay into a pointer is when it is the operand of the &
(address of) operator (the other is when used as a sizeof
operand). Thus, the expression, &temp
will yield a type of mpz_t*
that will be the address of the array:
if (flag) {
mpz_mul_ui(temp, in, 2);
arg = &temp; // RHS yields a `mpz_t*` type
}
In the case of the other assignment (i.e., when the given in
argument is the RHS), the situation is rather different and we must here use a cast. This is because that argument has already been converted to a pointer (its type is __mpz_struct*
, as indicated in my suggested code, above). Also, any attempt to take its address will almost certainly yield an inappropriate/incorrect value – it will be the address of the passed argument (a pointer), likely on the stack.
So, as the value of the in
argument is the address of the first element of the source array (used in the call to our function), we can cast that to an mpz_t*
type and it can then be used as the address of that source array:
else {
arg = (mpz_t*)in; // "in" has correct value but wrong type, so cast
}
So, if you really want to use the mpz_t*
type for your local arg
variable, you can. But I would caution against using pointers to arrays wherever possible: they are prone to cause unexpected issues, later on, especially when you start using pointer arithmetic or indexing operations.