I have this code here:
#include <stdio.h>
int add(const int* x, const int* y);
int main()
{
int x = 4;
int y = 3;
printf("%d", add(&x, &y));
return 0;
}
int add(int* x, int* y)
{
return *x *y;
}
When I compile it gives me an error: conflicting type for add
I know I have to put the const into the parameters of the function definition.
But if I add a typedef into the code like this:
#include <stdio.h>
typedef int* int_ptr;
int add(const int_ptr x, const int_ptr y);
int main()
{
int x = 4;
int y = 3;
printf("%d", add(&x, &y));
return 0;
}
int add(int_ptr x, int_ptr y)
{
return *x *y;
}
It compiled and gave me the output: 7
Why does this happen ?
CodePudding user response:
In const int* x
, const int
are the specifiers and *x
is the declarator. (This separation is specified by the formal grammar of C and is a reason why writing declarations as int* x
misrepresents the grammar.) This declaration says that *x
is a const int
, meaning x
is a pointer to const int
.
In typedef int* int_ptr
, typedef int
are the specifiers, and *int_ptr
is the declarator. The declaration says that *int_ptr
is an int
, and typedef
is a special specifier that modifies it so that int_ptr
is declared to be a type, rather than an object (variable).
In const int_ptr x
, const int_ptr
are the specifiers, and x
is the declaration. So this declaration says that x
is a const int_ptr
.
Here const
is modifying int_ptr
; const int_ptr x
says that x
is a const
pointer to an int
. In const int *x
, const
modifies int
, so it says *x
is a pointer to a const int
, meaning x
is a pointer to a const int
.
For the most part, when a function is declared with parameter type lists, the parameters must have compatible types in each declaration of the function. But there is an exception: C 2018 6.7.6.3 15 says:
… (In the determination of type compatibility and of a composite type, … each parameter declared with qualified type is taken as having the unqualified version of its declared type.)
This says that, when determining whether int add(const int_ptr x, const int_ptr y)
is compatible with int add(int_ptr x, int_ptr y)
, the const
qualifiers are ignored. Then the parameter types are the same, so the function declarations are compatible.
In int add(const int *x, const int *y)
, x
and y
are not qualified with const
. They point to const int
, but they themselves are not const
. That is, the pointer that is x
can be changed (it is not const
). The fact that it points to something that is const
does not make it const
. So the rule about ignoring qualifiers in function parameters does not apply here; there are no qualifiers on x
and y
. So int add(const int *x, const int *y)
and int add(int *x, int *y)
do not have compatible parameter types.
The reason for this rule about ignoring qualifiers in parameter types comes from the fact that qualifiers only affect objects, not values. If we have an object x
that is const
, it should not be changed (through that type). But, if we have gotten the int
value 3 from x
and are using it in an expression, there would be no meaning to saying 3 is const
. It is just a value being used in an expression; there is no memory assigned to it where we could store a new value that would change 3 to 4. Once the value of an object is retrieved from a const int
, it is just an int
.
Similarly, if we have a volatile int x
, the volatile
means the compiler must get the value of x
each time it is used in an expression, because volatile
means something could be changing the memory of x
in ways the compiler does not know about. But, once we have gotten the value of x
from memory, it is just a value. We are done with the “you have to get it from memory” part, so the volatile
has no more effect.
Since function arguments are always passed by value, the qualifiers are irrelevant to the caller. When a function is declared with void foo(const int x)
, the const
has a meaning inside the function: The compiler must issue a diagnostic if anything inside the function attempts to modify x
with its const
-qualified type. But the caller does not care: The caller only passes a value. When the function starts, it creates a local x
for itself, and that x
is const
, but it has no effect on the caller. So void foo(int x)
and void foo(const int x)
are compatible function declarations.