type compatible and const qualifier

N

nicolas.sitbon

Hi everybody,
This simple C code generates a warning:

#include <stdio.h>

static void func(int const * const * ptr)
{
printf("%d\n", **ptr);
}

int main(void)
{
int i = 13;
int *p_i = &i;
int **p_p_i = &p_i;

func(p_p_i); /* warning : passing argument 1 of ‘func’ from
incompatible pointer type */
func((int const **)p_p_i);
func((int const * const *)p_p_i);

return 0;
}

I have read the international Standard (C99 version), in particular,
6.7.3 (Type qualifiers) and 6.2.7 (Compatible type and composite
type), but I can't find the reason of different behaviours with the 3
function call.
Please can anyone help me to understand this, with, if possible,
reference to the international Standard.
Thanks all.
 
P

Peter Nilsson

This simple C code generates a warning:

#include <stdio.h>

static void func(int const * const * ptr)
{
   printf("%d\n", **ptr);
}

int main(void)
{
   int i = 13;
   int *p_i = &i;
   int **p_p_i = &p_i;

   func(p_p_i); /* warning : passing argument 1 of
‘func’ from incompatible pointer type */
   func((int const **)p_p_i);
   func((int const * const *)p_p_i);

   return 0;
}

I have read the international Standard (C99 version), in
particular, 6.7.3 (Type qualifiers) and 6.2.7 (Compatible
type and composite type), but I can't find the reason of
different behaviours with the 3 function call.

Please can anyone help me to understand this, with, if
possible, reference to the international Standard.

See the constraint 6.5.2.2p2. Realise that int ** is
not compatible with const int **. [The problem is often
dubbed 'const poisoning'.]
 
N

nicolas.sitbon

I have read it :

"2 If the expression that denotes the called function has a type that
includes a prototype, the
number of arguments shall agree with the number of parameters. Each
argument shall
have a type such that its value may be assigned to an object with
the unqualified version
of the type of its corresponding parameter."

but it doesn't say me why :
func((int const **)p_p_i); /* doesn't warn me */
func((int * const *)p_p_i); /* generate warning */

Is there any difference when the const qualifier is applied to an
arithmetic type or a pointer type?
Furthermore, if I anderstand correctly the standard, it seems to me
that the my argument is compatible with the unqualified version of the
function's parameter (that is unqualified).
 
B

Ben Bacarisse

I have read it :

"2 If the expression that denotes the called function has a type that
includes a prototype, the
number of arguments shall agree with the number of parameters. Each
argument shall
have a type such that its value may be assigned to an object with
the unqualified version
of the type of its corresponding parameter."

but it doesn't say me why :
func((int const **)p_p_i); /* doesn't warn me */
func((int * const *)p_p_i); /* generate warning */

You need to follow the chain starting at that reference. The key
starting point is that what you are permitted to pass to a function is
defined in terms of what you are permitted to assign to an object.
You need now to look at 6.5.16.1 "Simple assignment" in particular
the third bullet point paragraph 1.

To take a simpler case, you can pass a int * to a function that
expects a const int * because you can assign an int * value to a const
int * object. This is OK because (a) they are both pointers to
qualified or unqualified versions of compatible types (int *) and (b)
the type pointer to by the left (const int) has all the qualifiers of
the type pointer to by the right (int). In fact is has more, but that
is permitted.

Try to apply this reasoning to your exact case.
Is there any difference when the const qualifier is applied to an
arithmetic type or a pointer type?

Yes. That case is handled by different wording (the first bullet
point) but the effect is very similar. To paraphrase: qualifiers may
be added but not removed.
Furthermore, if I anderstand correctly the standard, it seems to me
that the my argument is compatible with the unqualified version of the
function's parameter (that is unqualified).

This wording is not exact enough. You probably think that the
unqualified version of const int * const * is int ** but that is not
how the standard uses the term. In fact, const int * const * is not a
qualified type at all -- it is an unqualified pointer type.

Even if it were, that is not how assignment (and thus parameter
passing) is defined. The wording of 6.5.2.2 p2 allows the
introduction of yet another qualification (a value of type T can be
passed to function that takes a const T parameter) but that has not
bearing on your example.

This is fiddly stuff, and C's rule is somewhat arbitrary rather than
entirely logical, but I am sure it will make sense eventually.
 
N

nicolas.sitbon

You need to follow the chain starting at that reference.  The key
starting point is that what you are permitted to pass to a function is
defined in terms of what you are permitted to assign to an object.
You need now to look at 6.5.16.1 "Simple assignment" in particular
the third bullet point paragraph 1.

Ok I have read the third bullet:
"— both operands are pointers to qualified or unqualified versions of
compatible types,
and the type pointed to by the left has all the qualifiers of the
type pointed to by the
right;"
And now I understand and I realize that is was a misconception of my
part.
This wording is not exact enough.  You probably think that the
unqualified version of const int * const * is int ** but that is not
how the standard uses the term.  In fact, const int * const * is not a
qualified type at all -- it is an unqualified pointer type.
No, I thought correctly,
I know what is a qualified or unqualified type, that's why I wrote
"(that is unqualified)".

Correct me if I say wrong:
my argument has type (int**)
the func parameter has type (int const * const *)
They both fall into the third bullet of 6.5.16.1 because they both
have pointer type.
But the type pointed, regardless its qualifier is not the same, it's
(int*) in my argument and (int const*) in the parameter, so they
aren't compatible.
 
B

Ben Bacarisse

Ok I have read the third bullet:
"— both operands are pointers to qualified or unqualified versions of
compatible types,
and the type pointed to by the left has all the qualifiers of the
type pointed to by the
right;"
And now I understand and I realize that is was a misconception of my
part.

No, I thought correctly,
I know what is a qualified or unqualified type, that's why I wrote
"(that is unqualified)".

OK, that was just a guess as to where your initial confusion came
from.
Correct me if I say wrong:
my argument has type (int**)
the func parameter has type (int const * const *)
They both fall into the third bullet of 6.5.16.1 because they both
have pointer type.

But the type pointed, regardless its qualifier is not the same, it's
(int*) in my argument and (int const*) in the parameter, so they
aren't compatible.

That is correct, yes.
 
V

vippstar

To take a simpler case, you can pass a int * to a function that
expects a const int * because you can assign an int * value to a const
int * object. This is OK because (a) they are both pointers to
qualified or unqualified versions of compatible types (int *) and (b)
the type pointer to by the left (const int) has all the qualifiers of
the type pointer to by the right (int). In fact is has more, but that
is permitted.

I have questions too, reading your post.
Since this is allowed, as you said:

const int *p = NULL;
int *q = p;

Then I understand that this is also allowed:

int *p = NULL;
const int *q = p;

and - this is also allowed:

int * const * p = NULL;
int **q = p;

p is a pointer to const pointer to int, and q is pointer to pointer to
int.
However, this is not allowed:

const int **p = NULL;
int **q = p;

Because *p is different type than *q
Am I correct?
 
N

Nate Eldredge

Ben Bacarisse said:
You need to follow the chain starting at that reference. The key
starting point is that what you are permitted to pass to a function is
defined in terms of what you are permitted to assign to an object.
You need now to look at 6.5.16.1 "Simple assignment" in particular
the third bullet point paragraph 1.

To take a simpler case, you can pass a int * to a function that
expects a const int * because you can assign an int * value to a const
int * object. This is OK because (a) they are both pointers to
qualified or unqualified versions of compatible types (int *) and (b)
the type pointer to by the left (const int) has all the qualifiers of
the type pointer to by the right (int). In fact is has more, but that
is permitted.

Let me see if I've got it.

First of all, I didn't realize that

int const * const *q;

is equivalent to

const int * const *q;

I'm more familiar with the second form. Indeed, the syntax in 6.7 makes
it appear that the type name, qualifiers, storage classes, and function
specifier ("inline") in a `declaration-specifiers' can appear in any
order with the same effect. I didn't know that.

Okay, so suppose we have

int **p;
int const * const * q;
q=p;

p is an unqualified pointer to (unqualified pointer to (unqualified int))
q is an unqualified pointer to (const-qualified pointer to (const-qualified int))

This would be legal if `int const *' and `int *' were compatible types,
because then p and q would be pointers to qualified or unqualified
versions of compatible types.

`int const *' is an unqualified pointer to (const-qualified int)
`int *' is an unqualified pointer to (unqualified int)

6.5.7.1 (2) says that the two pointer types `int const *' and `int *'
will be compatible if they are identically qualified pointers to
compatible types. Neither has any qualifiers, so they are identically
qualified, and it remains now to see whether `int const' and `int' are
compatible types.

6.7.3 (9) says that they must have the identically qualified version of
a compatible type. Oops; `int const' and `int' are not identically
qualified, even though they are differently qualified versions of
compatible types (`int' and `int'). So everything breaks down.

This is certainly counter-intuitive. Usually I think of the rules
regarding `const' as preventing you from removing a const qualifier, and
that's all. E.g. assigning a to b will be forbidden if you could modify
something through b which you couldn't through a. But in this case,
there's nothing you can modify through q, period, yet the assignment is
still forbidden. Odd.
 
N

nicolas.sitbon

I have questions too, reading your post.
Since this is allowed, as you said:

const int *p = NULL;
int *q = p;

No, that is forbidden :
"— both operands are pointers to qualified or unqualified versions of
compatible types,
and the type pointed to by the left has all the qualifiers of the
type pointed to by the
right;"
because the type pointed to by the left doesn't have all the qualifier
(in this case 'const') of the type pointed to b the right.
 
B

Ben Bacarisse

I have questions too, reading your post.
Since this is allowed, as you said:

const int *p = NULL;
int *q = p;

I said that the other way round was allowed. Note, also, that
initialisation has different rules to assignment, otherwise const int
one = 1; would not be permitted.
Then I understand that this is also allowed:

int *p = NULL;
const int *q = p;

That is OK (though, again, I was not talking about initialisation).
and - this is also allowed:

int * const * p = NULL;
int **q = p;

No. You have the two the wrong way round.

Just to be thorough: looking at it using the rules for assignment,
both are pointers to qualified or unqualified versions of compatible
types (one is int * const -- a const qualified version of the other
int *) but the type pointer to by the left (int *) does not have all
the qualifiers of the type pointer to by the right (int * const).

However, given:

int * const * p;
int **q = NULL;

the assignment p = q; /is/ permitted.

However, this is not allowed:

const int **p = NULL;
int **q = p;

Because *p is different type than *q
Am I correct?

Yes. The legalistic reasons is that the types of *p and *q are not
differently qualified versions of compatible types. Neither type is
qualified, and const int * is not compatible with int *.
 
B

Ben Bacarisse

Nate Eldredge said:
Let me see if I've got it.

First of all, I didn't realize that

int const * const *q;

is equivalent to

const int * const *q;

I'm more familiar with the second form. Indeed, the syntax in 6.7 makes
it appear that the type name, qualifiers, storage classes, and function
specifier ("inline") in a `declaration-specifiers' can appear in any
order with the same effect. I didn't know that.

It can make for some impenetrable code if people use an unfamiliar
order. I would write volatile const unsigned int, for example,
but I would not expect to see int const unsigned volatile. It is
worse with long long. (I one had a college who was a professor, a
reverend and a major in the army -- there is an agreed ordering to
the expected honorifics and it is a shame that C does not have one.)
Okay, so suppose we have

int **p;
int const * const * q;
q=p;

p is an unqualified pointer to (unqualified pointer to (unqualified int))
q is an unqualified pointer to (const-qualified pointer to (const-qualified int))

This would be legal if `int const *' and `int *' were compatible types,
because then p and q would be pointers to qualified or unqualified
versions of compatible types.
Yup.

`int const *' is an unqualified pointer to (const-qualified int)
`int *' is an unqualified pointer to (unqualified int)

6.5.7.1 (2) says that the two pointer types `int const *' and `int *'
will be compatible if they are identically qualified pointers to
compatible types. Neither has any qualifiers, so they are identically
qualified, and it remains now to see whether `int const' and `int' are
compatible types.
Yup.

6.7.3 (9) says that they must have the identically qualified version of
a compatible type. Oops; `int const' and `int' are not identically
qualified, even though they are differently qualified versions of
compatible types (`int' and `int'). So everything breaks down.

Yes. You have to go a ling way down the chain to get there but this
is the bottom line as to why these to can't be assigned.
This is certainly counter-intuitive. Usually I think of the rules
regarding `const' as preventing you from removing a const qualifier, and
that's all. E.g. assigning a to b will be forbidden if you could modify
something through b which you couldn't through a. But in this case,
there's nothing you can modify through q, period, yet the assignment is
still forbidden. Odd.

Some safe assignment are indeed forbidden giving rise to one of the
few cases where a cast is required.
 
V

vippstar

No, that is forbidden :
"— both operands are pointers to qualified or unqualified versions of
compatible types,
and the type pointed to by the left has all the qualifiers of the
type pointed to by the
right;"
because the type pointed to by the left doesn't have all the qualifier
(in this case 'const') of the type pointed to b the right.


Thanks for clearing this up.
Thanks goes to Bacarisse too for his help in this post.
<[email protected]>

It's a difficult "rule" to get the hang of but I'll manage I think ;-)
 
O

Old Wolf

static void func(int const * const * ptr)
{
}

int main(void)
{
   int i = 13;
   int *p_i = &i;
   int **p_p_i = &p_i;

   func(p_p_i); /* warning : passing argument 1 of ‘func’ from
incompatible pointer type */

This is a 'bug' in the C standard; there's no
reason why this conversion should not be
permitted. Unfortunately, you just have to
either use a cast, or forego some const-correctmess.

(Note that C++ allows this conversion, and
that it is quite different to the conversion
int ** -> const int **
which should not be, and is not, implicit.)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,904
Latest member
HealthyVisionsCBDPrice

Latest Threads

Top