Pointer / Const Warnings

C

Christopher Key

Hello,

Can anyone suggest why gcc (-W -Wall) complains,

test.c:22: warning: passing arg 1 of `f' from incompatible pointer type

when compiling the following code? Change the declation of f to,

void f(int *const *const p, int *const *const q) {

prevents the warning, but I just can't see why this should be.


Many thanks,

Chris



#include <stdlib.h>
#include <stdio.h>

void f(const int *const *const p, int *const *const q);

int main() {

int **p, **q, i;

p = calloc(2, sizeof(int *));
q = calloc(2, sizeof(int *));
for (i=0; i<2; i++) {
p = calloc(2, sizeof(int));
q = calloc(2, sizeof(int));
}

p[0][0] = 41;
p[0][1] = 40;
p[1][0] = 39;
p[1][1] = 38;

f(p, q);

fprintf(stderr, "%d %d %d %d", q[0][0], q[0][1], q[1][0], q[1][1]);

for (i=0; i<2; i++) {
free(p);
free(q);
}
free(p);
free(q);

return 0;
}

void f(const int *const *const p, int *const *const q) {
q[0][0] = p[0][0] + 1;
q[0][1] = p[0][1] + 2;
q[1][0] = p[1][0] + 3;
q[1][1] = p[1][1] + 4;
}
 
U

user923005

Hello,

Can anyone suggest why gcc (-W -Wall) complains,

test.c:22: warning: passing arg 1 of `f' from incompatible pointer type

when compiling the following code? Change the declation of f to,

void f(int *const *const p, int *const *const q) {

prevents the warning, but I just can't see why this should be.

Many thanks,

Chris

#include <stdlib.h>
#include <stdio.h>

void f(const int *const *const p, int *const *const q);

int main() {

int **p, **q, i;

p = calloc(2, sizeof(int *));
q = calloc(2, sizeof(int *));
for (i=0; i<2; i++) {
p = calloc(2, sizeof(int));
q = calloc(2, sizeof(int));
}

p[0][0] = 41;
p[0][1] = 40;
p[1][0] = 39;
p[1][1] = 38;

f(p, q);

fprintf(stderr, "%d %d %d %d", q[0][0], q[0][1], q[1][0], q[1][1]);

for (i=0; i<2; i++) {
free(p);
free(q);
}
free(p);
free(q);

return 0;

}

void f(const int *const *const p, int *const *const q) {
q[0][0] = p[0][0] + 1;
q[0][1] = p[0][1] + 2;
q[1][0] = p[1][0] + 3;
q[1][1] = p[1][1] + 4;



}


I agree with you. I don't think that the compiler should ever
complain about a *decrease* in pointer capability. I think GCC is
blowing pickle smoke and horse-feathers here. I suppose you could
cast it like this:

#include <stdlib.h>
#include <stdio.h>

static void douglass_adams(const int *const * const p, int *const *
const q);

int main()
{
int **p,
**q,
i;

p = calloc(2, sizeof(int *));
q = calloc(2, sizeof(int *));
if (p && q) {
for (i = 0; i < 2; i++) {
p = calloc(2, sizeof(int));
q = calloc(2, sizeof(int));
if (p == NULL || q == NULL) {
printf("Memory allocation error at %s:%d.\n",
__FILE__, __LINE__);
exit(EXIT_FAILURE);
}
}

p[0][0] = 41;
p[0][1] = 40;
p[1][0] = 39;
p[1][1] = 38;

douglass_adams((const int *const * const) p, q);

fprintf(stderr, "%d %d %d %d", q[0][0], q[0][1], q[1][0], q[1]
[1]);

for (i = 0; i < 2; i++) {
free(p);
free(q);
}
free(p);
free(q);
}
return 0;
}


static void douglass_adams(const int *const * const p, int *const *
const q)
{
q[0][0] = p[0][0] + 1;
q[0][1] = p[0][1] + 2;
q[1][0] = p[1][0] + 3;
q[1][1] = p[1][1] + 4;
}
 
C

christian.bau

The warning is confusing, but completely legitimate. The reason is a
bit more complicated.

Lets say I have a pointer variable int* x and another pointer variable
const int* y. Clearly I shouldn't be able to just assign y to x
without an explicit cast, right? So an assignment x = y should be
illegal.

Now lets say I have two other pointer variables int* *p and const int*
*q. The assignment *p = y is illegal, just as x = y was illegal, but
*q = y is legal.

And now I write p = &x; q = p; . The last assignment is what the
compiler complained about. And now you can see why: Once you have done
this, you can write *q = y; which assigns y to x which we didn't want
to allow. So the following sequence of instructions would assign to a
const int value without any casts:

const int i = 10;
const int* y = &i;
int* x;
int* *p = &x;
const int* *q = p; // Warning
*p = y;
*x = 20;
 
U

user923005

The warning is confusing, but completely legitimate. The reason is a
bit more complicated.

Lets say I have a pointer variable int* x and another pointer variable
const int* y. Clearly I shouldn't be able to just assign y to x
without an explicit cast, right? So an assignment x = y should be
illegal.

Right, but in this case we have a decrease in pointer capability. So
(to me) it is more analogous to:

int main(void)
{
int a;
int *x = &a;
const int *y;
y = x; /* Totally harmless, of course */
return 0;
}

Now lets say I have two other pointer variables int* *p and const int*
*q. The assignment *p = y is illegal, just as x = y was illegal, but
*q = y is legal.

And now I write p = &x; q = p; . The last assignment is what the
compiler complained about. And now you can see why: Once you have done
this, you can write *q = y; which assigns y to x which we didn't want
to allow. So the following sequence of instructions would assign to a
const int value without any casts:

const int i = 10;
const int* y = &i;
int* x;
int* *p = &x;
const int* *q = p; // Warning
*p = y;
*x = 20;

In this case:

int main(void)
{
const int i = 10;
const int *y = &i;
int *x;
int **p = &x;
const int **q = p; /* Warning? I see no danger from it.
We just have a less capable pointer to pointer to i in q. */
*p = y;
*x = 20;
return 0;
}

I think I am going to need some help to understand what could possibly
be bad about this assignment.
 
C

christian.bau

Right, but in this case we have a decrease in pointer capability. So
(to me) it is more analogous to:

int main(void)
{
int a;
int *x = &a;
const int *y;
y = x; /* Totally harmless, of course */
return 0;



}



In this case:

int main(void)
{
const int i = 10;
const int *y = &i;
int *x;
int **p = &x;
const int **q = p; /* Warning? I see no danger from it.
We just have a less capable pointer to pointer to i in q. */
*p = y;
*x = 20;
return 0;

}

I think I am going to need some help to understand what could possibly
be bad about this assignment.

Step by step:

1: const int i = 10;
2: const int* y = &i;
3: int* x;
4: int* *p = &x;
5: const int* *q = p; // Warning
6: *q = y; // Oops, I got that wrong in my first post. *q, not *p.
7: *x = 20;

1. A const int is initialised with a value of 10. Can't be changed
anymore.
2. y points to i. You can't modify i using y.
3. x is uninitialised.
4. p points to x.
5. q also points to x. Next statement shows why this is dangerous.
6. This assigns y to x because q points to x. That is it assigns a
const int* to an int* which shouldn't happen. (My original post got
that wrong. The assignment *p = y also assigned y to x, but here the
compiler would have noticed).
7. Really bad; this assigns 20 to i.

So the problem with the assignment int** to const int** is that in the
next statement, a const int* can be assigned to an int* without the
compiler noticing and complaining. You see the obvious loss of
capability which is safe: When assigning an int** to a const int**,
you lose the capability of assigning an int value with double
indirection. But you also gain a dangerous and unsafe capability: You
gain the capability of storing a const int* in a location that is only
allowed to hold an int*.

While *q is less capable than *p (*p allows you to store integers
while *q only lets you read integers), q is actually more capable than
p, because q allows you to store both int* and const int*, while q
only allows you to store int*.
 
U

user923005

Step by step:

1: const int i = 10;
2: const int* y = &i;
3: int* x;
4: int* *p = &x;
5: const int* *q = p; // Warning
6: *q = y; // Oops, I got that wrong in my first post. *q, not *p.
7: *x = 20;

1. A const int is initialised with a value of 10. Can't be changed
anymore.
2. y points to i. You can't modify i using y.
3. x is uninitialised.
4. p points to x.
5. q also points to x. Next statement shows why this is dangerous.
6. This assigns y to x because q points to x. That is it assigns a
const int* to an int* which shouldn't happen. (My original post got
that wrong. The assignment *p = y also assigned y to x, but here the
compiler would have noticed).
7. Really bad; this assigns 20 to i.

So the problem with the assignment int** to const int** is that in the
next statement, a const int* can be assigned to an int* without the
compiler noticing and complaining. You see the obvious loss of
capability which is safe: When assigning an int** to a const int**,
you lose the capability of assigning an int value with double
indirection. But you also gain a dangerous and unsafe capability: You
gain the capability of storing a const int* in a location that is only
allowed to hold an int*.

While *q is less capable than *p (*p allows you to store integers
while *q only lets you read integers), q is actually more capable than
p, because q allows you to store both int* and const int*, while q
only allows you to store int*.

OK, now I get it. That could cause subtle problems and it is good to
understand it.
Thanks for the detailed explanation.
 
C

Christopher Key

christian.bau said:
Step by step:

1: const int i = 10;
2: const int* y = &i;
3: int* x;
4: int* *p = &x;
5: const int* *q = p; // Warning
6: *q = y; // Oops, I got that wrong in my first post. *q, not *p.
7: *x = 20;

1. A const int is initialised with a value of 10. Can't be changed
anymore.
2. y points to i. You can't modify i using y.
3. x is uninitialised.
4. p points to x.
5. q also points to x. Next statement shows why this is dangerous.
6. This assigns y to x because q points to x. That is it assigns a
const int* to an int* which shouldn't happen. (My original post got
that wrong. The assignment *p = y also assigned y to x, but here the
compiler would have noticed).
7. Really bad; this assigns 20 to i.

So the problem with the assignment int** to const int** is that in the
next statement, a const int* can be assigned to an int* without the
compiler noticing and complaining. You see the obvious loss of
capability which is safe: When assigning an int** to a const int**,
you lose the capability of assigning an int value with double
indirection. But you also gain a dangerous and unsafe capability: You
gain the capability of storing a const int* in a location that is only
allowed to hold an int*.

While *q is less capable than *p (*p allows you to store integers
while *q only lets you read integers), q is actually more capable than
p, because q allows you to store both int* and const int*, while q
only allows you to store int*.

Thanks very much Christian, I'd come across this issue in a FAQ
recently, but I now understand it a lot better. I'm still not entirely
sure why the compiler is complaining though, as I'm not assigning 'const
int **q = p', but rather 'const int *const *const q = p', which would
then make 6 impossible. As a simpler example, take the following, for
which gcc still warns:


#include <stdlib.h>
#include <stdio.h>

void f(const int *const *const p);

int main() {
int x = 10;
int *y = &x;
int **p = &y;

f(p);

return 0;
}

void f(const int *const *const p) {
/* What can I do here that's unsafe??? */
fprintf(stdout, "p is %d", **p);
}


Regards,

Chris
 
C

christian.bau

Thanks very much Christian, I'd come across this issue in a FAQ
recently, but I now understand it a lot better. I'm still not entirely
sure why the compiler is complaining though, as I'm not assigning 'const
int **q = p', but rather 'const int *const *const q = p', which would
then make 6 impossible. As a simpler example, take the following, for
which gcc still warns:

#include <stdlib.h>
#include <stdio.h>

void f(const int *const *const p);

int main() {
int x = 10;
int *y = &x;
int **p = &y;

f(p);

return 0;

}

void f(const int *const *const p) {
/* What can I do here that's unsafe??? */
fprintf(stdout, "p is %d", **p);

}

This is gcc. gcc has a tendency to produce warnings in some cases that
should be hard errors. Normally, a compiler should give warnings in
situations where the compiler writer thinks you might have done
something that you didn't intend to do, even though they are legal C,
but gcc also gives mere warnings in cases that are errors. In the
first case, the compiler writer _should_ be careful only to give a
warning if damage is actually possible, in the case of errors the
language determines whether there is an error or not, and the compiler
doesn't have much choice. This kind of assignment is I think a real
error, so even though nothing can happen in this particular case,
there should be an error message.

Yes, the middle "const" makes it less likely that an error would
occur. It is still possible. See this example:

void change_if_not_const (const int* p, int ismodifiable, int
newvalue) {
if (ismodifiable)
* (int *) p = newvalue;
}

int x;
const int y;

change_if_not_const (&x, 1, 100);
change_if_not_const (&y, 0, 100);

The function is quite reasonable, and the calls are correct and safe.
Now I write a similar function:

void change_if_not_const (const int* const* p, int ismodifiable, const
int* newvalue) {
if (ismodifiable)
* (const int* *p) = newvalue;
}

As in the first example, passing a "const int* const*" is of course
perfectly legal. Passing a "const int* *" is fine as well, but passing
an "int **" is dangerous and must not be allowed.
 
B

Ben Bacarisse

christian.bau said:
This is gcc. gcc has a tendency to produce warnings in some cases that
should be hard errors.

I don't think that is what the OP is talking about. The code is
"safe" (f can only modify a const object by casting away const) but
the call is not allowed anyway. The OP expected the code to compile
without any diagnostic (as it wound under a C++ compiler for
example). In fact the standard insists that a diagnostic be issued.

See
http://groups.google.com/group/comp.lang.c/msg/fb30f3feeeda0049
for the details.

The reason is simply that the rule in the standard is simple to state
and implement. If you follow that thread to the end you will see a
remark that suggests the complexity adding of C's extra restrict
qualifier meant that no one was perpared to work though the details to
propose an alternative.

<examples snipped>
Your examples use casts so I don't think they shed any more light on
why int ** can't be passed to a int const *const *const parameter.
 
C

Charlie Gordon

Christopher Key said:
#include <stdlib.h>
#include <stdio.h>

void f(const int *const *const p);

int main() {
int x = 10;
int *y = &x;
int **p = &y;

f(p);

return 0;
}

void f(const int *const *const p) {
/* What can I do here that's unsafe??? */
fprintf(stdout, "p is %d", **p);
}

C currently does not allow it, even though it is safe.
I think D allows it, but with a different syntax and slightly different
semantics.
 
C

Christopher Key

Ben said:
I don't think that is what the OP is talking about. The code is
"safe" (f can only modify a const object by casting away const) but
the call is not allowed anyway. The OP expected the code to compile
without any diagnostic (as it wound under a C++ compiler for
example). In fact the standard insists that a diagnostic be issued.

See
http://groups.google.com/group/comp.lang.c/msg/fb30f3feeeda0049
for the details.

The reason is simply that the rule in the standard is simple to state
and implement. If you follow that thread to the end you will see a
remark that suggests the complexity adding of C's extra restrict
qualifier meant that no one was perpared to work though the details to
propose an alternative.

<examples snipped>
Your examples use casts so I don't think they shed any more light on
why int ** can't be passed to a int const *const *const parameter.

Thanks Ben,

I think that answers my query.

Could you tell me what the recommended practice is in this case? What
I'm trying to do is to pass a pointer to an array of pointers each
pointer to an array of ints, probably best described by:

void f(const int *const p[]);

although this excludes the assertion that p is const too. I'd like code
calling f to be able to be sure that the data they passed in remains
unchanged, and for the compiler to be able to optimise around that if
possible.

Regards,

Chris
 
B

Ben Bacarisse

Christopher Key said:
Ben Bacarisse wrote:

Thanks Ben,

I think that answers my query.

Could you tell me what the recommended practice is in this case?

Not really. I suspect that style guides and personal preferences
differ over what to do.
What
I'm trying to do is to pass a pointer to an array of pointers each
pointer to an array of ints, probably best described by:

void f(const int *const p[]);

although this excludes the assertion that p is const too.

That is almost universally agreed to be OK leave that (first) const
out since f can't modify p itself in any significant (non-local) way.
I'd like
code calling f to be able to be sure that the data they passed in
remains unchanged, and for the compiler to be able to optimise around
that if possible.

I would leave the 'const's in the parameter type and accept that I
need a cast to pass the parameter. I'd be tempted to add a wrapper to
do this so the cast appears only once:

void f_safe(int *p[])
{
f((const int **)p);
}

The other way round would be to change the prototype for f and put the
cast at the top of the function, initialising a local variable (in
which case I would add the final const):

void f(int *dummy[])
{
const int *const *const p = (const int **)dummy;
...
}

Note that I have used the minimal cast required (const int **) -- the
const "between" the stars is not needed.

Of course, the simplest solution is just to loose the outer const
altogether.
 

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,483
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top