Implicit addition of const qualifiers

R

Roger Leigh

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

A lot of functions use const pointer arguments. If I have a non-const
pointer, it is transparently made const when I pass it to the
function, e.g. char * -> const char *. However, this does not appear
to work when I add another level of indirection:

void test1 (char **value)
{}

void test2 (const char **value)
{}

void test3 (const char * const *value)
{}

void test4 (char * const *value)
{}

int
main (void)
{
char **v;

test1(v);
test2(v);
test3(v);
test4(v);

return 0;
}

$ gcc -Wall -pedantic -o test1 test.c
test.c: In function ‘main’:
test.c:19: warning: passing argument 1 of ‘test2’ from
incompatible
pointer type
test.c:20: warning: passing argument 1 of ‘test3’ from
incompatible pointer type

Why is this sort of conversion requiring an explicit cast? Adding
(const char * const *) casts all over the place doesn't really improve
code readability, even if it does give me const correctness!


Thanks,
Roger

- --
Roger Leigh
Printing on GNU/Linux? http://gimp-print.sourceforge.net/
Debian GNU/Linux http://www.debian.org/
GPG Public Key: 0x25BFB848. Please sign and encrypt your mail.
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.1 (GNU/Linux)
Comment: Processed by Mailcrypt 3.5.8 <http://mailcrypt.sourceforge.net/>

iD8DBQFCzGCVVcFcaSW/uEgRArUGAKDoU72gbXjcIj+vTl3p6G1HD0B5lACgvwxV
ih47/4KIkBWTCY8lVM5nndk=
=v5Ep
-----END PGP SIGNATURE-----
 
C

Christian Bau

Roger Leigh said:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

A lot of functions use const pointer arguments. If I have a non-const
pointer, it is transparently made const when I pass it to the
function, e.g. char * -> const char *. However, this does not appear
to work when I add another level of indirection:

void test1 (char **value)
{}

void test2 (const char **value)
{}

void test3 (const char * const *value)
{}

void test4 (char * const *value)
{}

int
main (void)
{
char **v;

test1(v);
test2(v);
test3(v);
test4(v);

return 0;
}

$ gcc -Wall -pedantic -o test1 test.c
test.c: In function Œmain¹:
test.c:19: warning: passing argument 1 of Œtest2¹ from
incompatible
pointer type
test.c:20: warning: passing argument 1 of Œtest3¹ from
incompatible pointer type

Why is this sort of conversion requiring an explicit cast? Adding
(const char * const *) casts all over the place doesn't really improve
code readability, even if it does give me const correctness!

Lets say you have a char* (points to chars that can be modified), and a
function that takes a const char* parameter (points to chars that cannot
be modified). The language allows an automatic cast, because no harm
will be done: All that happens is that the function cannot easily modify
the chars, even though it would be ok (from the callers point of view)
to modify them. It is also obvious that in the opposite situation, an
automatic cast shouldn't be allowed by the C language: Such a cast would
mean that the function could modify chars that are actually const; a
very dangerous thing to do.

Now what you have is a char** (points to pointers to modifiable chars),
and the function takes an argument const char** (points to pointers to
unmodifiable chars). Now lets say I have an array somewhere

static const array [10];

If I have a const char**, lets say const char** p, then an assignment

*p = &array [0];

would be perfectly legal. However, if I have another pointer char** q
(note: Not const char**) then if the language allowed me to write

*q = &array [0];

that would be a very dangerous thing: I cannot use *p to modify a char,
but I can use *q, for example (*q) [2] = '\0'; . That is why the
compiler doesn't allow the assignment *q = &array [0] - allowing this
assignment would mean that I could modify a const char without using a
cast. That cannot be allowed.

Now to your problem: You have a pointer char** q. Your function takes an
argument const char** p. If you could pass the char** q to the function
without a cast, then the assignment

*p = &array [0];

could be performed within the function, which would do effectively the
same thing as writing

*q = &array [0];

outside the function, which as we have seen must not be allowed. The
automatic conversion from char* to const char* is safe - it doesn't let
you do anything that you couldn't do without the conversion. The
automatic conversion from char** to const char** is NOT safe: Such a
conversion would allow you to store a const char* where a (non const)
char* is expected, and consequently to modify a const char which must
not be allowed.

If you think carefully about it, you will find that the following
conversions are safe resp. unsafe (and therefore are allowed /
disallowed to happen automatically):

char* -> const char* is safe
const char* -> char* is unsafe

char** -> const char** is unsafe
const char** -> char** is safe

char*** -> const char*** is safe
const char*** -> char*** is unsafe

and so on.
 
M

Me

A lot of functions use const pointer arguments. If I have a non-const
pointer, it is transparently made const when I pass it to the
function, e.g. char * -> const char *. However, this does not appear
to work when I add another level of indirection: *snip*
test.c:19: warning: passing argument 1 of 'test2' from
incompatible
pointer type
test.c:20: warning: passing argument 1 of 'test3' from
incompatible pointer type

Why is this sort of conversion requiring an explicit cast?

This is taken directly from the C++ standard:

[Note: if a program could assign a pointer of type T** to a pointer of
type const T** (that is, if line //1 below was allowed), a program
could inadvertently modify a const object (as it is done on line //2).
For example,
int main () {
const char c = 'c';
char * pc;
const char ** pcc = & pc ; // 1: not allowed
*pcc = & c;
*pc = 'C'; // 2: modifies a const object
}
-end note]
Adding
(const char * const *) casts all over the place doesn't really improve
code readability, even if it does give me const correctness!

Don't do that. The types are considered not to be compatible and if you
access it, you break aliasing rules (which GCC is strict about and can
generate code that gives you incorrect results or crashes). More info
about aliasing in plain English:

http://mail-index.netbsd.org/tech-kern/2003/08/11/0001.html
 
L

Lawrence Kirby

On Thu, 07 Jul 2005 00:47:06 +0100, Christian Bau wrote:

....
If you think carefully about it, you will find that the following
conversions are safe resp. unsafe (and therefore are allowed /
disallowed to happen automatically):

char* -> const char* is safe
const char* -> char* is unsafe
OK

char** -> const char** is unsafe
const char** -> char** is safe

These are both unsafe.
char*** -> const char*** is safe
const char*** -> char*** is unsafe

As are these. C allows

char ** -> char *const *
char *** -> char **const *

I believe C++ also allows:

char *** -> char *const *const *
char *** -> const char *const *const *
char (*)[N] -> const char (*)[N]

I.e. if you add const all "higher" intermediate levels must also be const
qualified. For whatever reasons the C committee decided not to support
this, which is a pity.

Lawrence
 
R

Roger Leigh

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Lawrence Kirby said:
On Thu, 07 Jul 2005 00:47:06 +0100, Christian Bau wrote:

...


These are both unsafe.

For the top example, I have a pointer to pointer-to-char being cast
to pointer to pointer-to-const-char. I don't see how any addition of
const qualifiers makes it unsafe. I now can't modify the char through
the pointer (**p = 'c'), but I fail to see how there are other issues.
As are these. C allows

I'm afraid I still fail to see why the first is unsafe.
char ** -> char *const *
char *** -> char **const *

I believe C++ also allows:

char *** -> char *const *const *
char *** -> const char *const *const *
char (*)[N] -> const char (*)[N]

I.e. if you add const all "higher" intermediate levels must also be const
qualified. For whatever reasons the C committee decided not to support
this, which is a pity.

In my code, I have "char *type[]" passed to functions as "char **".
This is an argv-like array of pointers-to-strings. Most of the
functions don't modify the pointed-to objects. They might iterate
through them, copy them, etc.. In this case, I'm not modifying the
string array nor the strings themselves, so "const char * const *"
would be ideal, so it's obvious to the caller that it's read-only.
It's also useful to indicate functions that would not be safe to use
with truly const arrays of strings.

My only problem here is the compiler warning about the conversion
(char ** -> const char * const *), but I'm afraid I still don't
understand why this it, even with your examples.


Regards,
Roger

- --
Roger Leigh
Printing on GNU/Linux? http://gimp-print.sourceforge.net/
Debian GNU/Linux http://www.debian.org/
GPG Public Key: 0x25BFB848. Please sign and encrypt your mail.
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.1 (GNU/Linux)
Comment: Processed by Mailcrypt 3.5.8 <http://mailcrypt.sourceforge.net/>

iD8DBQFC2p5yVcFcaSW/uEgRAp/dAJwP54PPxBLIpDIai+pUkbxH63UJsACdFgov
8/LnVcHfJrihmxLIo9y7Tpo=
=bvV6
-----END PGP SIGNATURE-----
 
N

Netocrat

For the top example, I have a pointer to pointer-to-char being cast
to pointer to pointer-to-const-char. I don't see how any addition of
const qualifiers makes it unsafe. I now can't modify the char through
the pointer (**p = 'c'), but I fail to see how there are other issues.

You didn't examine Me's example closely enough. Here it is again with
commentary:

int main (void) {
const char c = 'c'; /* we declare c to be const-protected */
char * pc;
const char ** pcc = & pc ; /* we assign the address of a non-const
* char pointer to a doubly indirected const
* char pointer. You want want this cast
* conversion to be automatic */
*pcc = & c; /* now we assign the address of our
* const-protected variable c to the pointer
* that pcc points to, which is pc.
* i.e. this line is equivalent to pc = &c
* which plainly should not be allowed because
* it removes the const-protection from c. */
*pc = 'C'; /* now we can assign to our supposedly
* const-protected c variable by dereferencing
* our non-const pc pointer. Why? Because the
* cast from char ** to const char ** was
* automatically allowed. */
}
I'm afraid I still fail to see why the first is unsafe.

By extension for the same reason as Me's example shows.

In my code, I have "char *type[]" passed to functions as "char **".
This is an argv-like array of pointers-to-strings. Most of the
functions don't modify the pointed-to objects. They might iterate
through them, copy them, etc.. In this case, I'm not modifying the
string array nor the strings themselves, so "const char * const *"
would be ideal, so it's obvious to the caller that it's read-only.
It's also useful to indicate functions that would not be safe to use
with truly const arrays of strings.

My only problem here is the compiler warning about the conversion
(char ** -> const char * const *), but I'm afraid I still don't
understand why this it, even with your examples.

That one semantically is still safe, since it would disallow the
problem assignment *pcc = &c in the above example.

It is not allowed by C though, I don't know why.
 
D

Dave Thompson

That one semantically is still safe, since it would disallow the
problem assignment *pcc = &c in the above example.

It is not allowed by C though, I don't know why.

AIUI because it was simpler to think of, explain, and prove safe, the
single simple rule T * -> qualified T * . Remember that qualification
itself was new in C90 and there wasn't widespread experience with
implementation and use. C++, with the benefit of hindsight and perhaps
more time to analyze, does allow it.

- David.Thompson1 at worldnet.att.net
 
L

lawrence.jones

Dave Thompson said:
AIUI because it was simpler to think of, explain, and prove safe, the
single simple rule T * -> qualified T * . Remember that qualification
itself was new in C90 and there wasn't widespread experience with
implementation and use. C++, with the benefit of hindsight and perhaps
more time to analyze, does allow it.

Correct. Unfortunately, the C++ standard describes the language
sufficiently differently from the way C did that it isn't possible to
just pick up the words from the C++ standard and plug them into the C
standard. C also adopted the additional "restrict" qualifier, which
will require a revision of the rules since it doesn't seem to behave the
same as const and volatile.

-Larry Jones

Is it too much to ask for an occasional token gesture of appreciation?!
-- Calvin
 

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

Forum statistics

Threads
473,780
Messages
2,569,608
Members
45,241
Latest member
Lisa1997

Latest Threads

Top