Write-protecting objects addressed via indirect pointers

M

Michael Schumacher

Hello everybody,

this question came up recently in a local C NG, and it's about
how to correctly apply the "const" qualifier, when the object
(i.e., a variable) is "double-referenced" (pointer to pointer
to object).

Example: say, we have a character array,
char mystring[100];
and a pointer to some character in that array:
char *ptr = &mystring[42];

Now we'd like to define a function, say "myparse()", that
takes "&ptr" as a parameter, so that it can advance "*ptr"
to the end of the current token. However, we do *not* want
"myparse()" to modify the characters (i.e., the compiler should
emit an error message if things like "**ptr = 'x';" occurr),
*and* we don't want the compiler to emit a warning when
calling "myparse (&ptr);" due to incompatible pointer types.

I will intentionally _not_ show you all the variants I already
tried, in order to not confuse or "bias" you. To summarize:

void
myparse ("type of pointer to pointer to constant chars" x)
{
++(*x); // should be allowed
++(**x); // should produce an error message
}

void
foo (void)
{
char mystring[100];
char *ptr = &mystring[42];

myparse (&ptr); // should work w/out any cast or warning!
}

Any ideas how to accomplish this in ISO-C (FWIW, I'm using gcc
4.3.2 and the options "-c -Wall -std=c99 -pedantic")?


Thanks,
mike
 
M

Michael Schumacher

Richard said:
Hello everybody,

this question came up recently in a local C NG, and it's about
how to correctly apply the "const" qualifier, when the object
(i.e., a variable) is "double-referenced" (pointer to pointer
to object).

Example: say, we have a character array,
char mystring[100];
and a pointer to some character in that array:
char *ptr = &mystring[42];

Now we'd like to define a function, say "myparse()", that
takes "&ptr" as a parameter, so that it can advance "*ptr"
to the end of the current token. However, we do *not* want
"myparse()" to modify the characters (i.e., the compiler should
emit an error message if things like "**ptr = 'x';" occurr),
*and* we don't want the compiler to emit a warning when
calling "myparse (&ptr);" due to incompatible pointer types.

int myparse(const char **tokenptrptr, size_t max)
{
int rc = default_return_code;

const char *endptr = *tokenptrptr;
/* move endptr as desired, and update return code -
when done, do this: */
*tokenptrptr = endptr;
return rc;
}

(Using the temp like that makes the code much easier to read...)

Yep (well, maybe), but the question is whether/how it's possible
to declare "tokenptrptr" straight-forward, with no intermediate
steps, such that the two required constraints (error message when
modifying "**tokenptr", and no warning for "myparse (&tokenptr);")
will be satisfied. Your solution sorta works around this, but it
still causes a warning for the function call. Any other ideas?


mike
 
M

Michael Schumacher

Richard said:
Michael Schumacher said:
this question came up recently in a local C NG, and it's about
how to correctly apply the "const" qualifier, when the object
(i.e., a variable) is "double-referenced" (pointer to pointer
to object).

Example: say, we have a character array,
char mystring[100];
and a pointer to some character in that array:
char *ptr = &mystring[42];

Now we'd like to define a function, say "myparse()", that
takes "&ptr" as a parameter, so that it can advance "*ptr"
to the end of the current token. However, we do *not* want
"myparse()" to modify the characters (i.e., the compiler should
emit an error message if things like "**ptr = 'x';" occurr),
*and* we don't want the compiler to emit a warning when
calling "myparse (&ptr);" due to incompatible pointer types.

I will intentionally _not_ show you all the variants I already
tried, in order to not confuse or "bias" you. To summarize:

void
myparse ("type of pointer to pointer to constant chars" x)
{
++(*x); // should be allowed
++(**x); // should produce an error message
}

maybe

const char * * x

Close, but no cigar: it causes a warning for the function call:

It should be perfectly okay for a compiler to emit a warning if
some constant value is passed as an lvalue, but why should it
gripe if a function voluntarily resigns (is that the right term?)
its right to modify an otherwise perfectly writeable object?


Cheers,
mike
 
K

Keith Thompson

Michael Schumacher said:
this question came up recently in a local C NG, and it's about
how to correctly apply the "const" qualifier, when the object
(i.e., a variable) is "double-referenced" (pointer to pointer
to object).

Example: say, we have a character array,
char mystring[100];
and a pointer to some character in that array:
char *ptr = &mystring[42];

Now we'd like to define a function, say "myparse()", that
takes "&ptr" as a parameter, so that it can advance "*ptr"
to the end of the current token. However, we do *not* want
"myparse()" to modify the characters (i.e., the compiler should
emit an error message if things like "**ptr = 'x';" occurr),
*and* we don't want the compiler to emit a warning when
calling "myparse (&ptr);" due to incompatible pointer types.

I will intentionally _not_ show you all the variants I already
tried, in order to not confuse or "bias" you. To summarize:

void
myparse ("type of pointer to pointer to constant chars" x)
{
++(*x); // should be allowed
++(**x); // should produce an error message
}

void
foo (void)
{
char mystring[100];
char *ptr = &mystring[42];

myparse (&ptr); // should work w/out any cast or warning!
}

Any ideas how to accomplish this in ISO-C (FWIW, I'm using gcc
4.3.2 and the options "-c -Wall -std=c99 -pedantic")?

Get yourself a copy of the "cdecl" program.

% cdecl
Type `help' or `?' for help
cdecl> declare x as const pointer to pointer to char
char ** const x
cdecl> exit
%
 
N

Nobody

*and* we don't want the compiler to emit a warning when
calling "myparse (&ptr);" due to incompatible pointer types.

Tough luck. The types really are incompatible, and the compiler is obliged
to generate a diagnostic.

A pointer to immutable (const) data and a pointer to mutable data aren't
required to use the same representation. And while the compiler can
implicitly convert an individual pointer, it can't convert a whole array
of pointers.

If you pass a "char *" to a function expecting a "const char *", the
compiler can simply convert it. But if you pass a "char **" to a function
expecting a "const char **", it can't convert the array of "char *"s to
the array of "const char *"s which the callee is expecting.
 
B

Ben Bacarisse

Michael Schumacher said:
... Your solution sorta works around this, but it
still causes a warning for the function call. Any other ideas?

There is no perfect solution. The type const char ** is the correct
one but the rules of C *require* a diagnostic when you pass an
argument of type char ** to such a function. If you use a cast:

myparse((const **)&ptr);

you can usually avoid the warning, but a compiler is permitted to warn
about anything at all and some might choose to do so for that call.

The other option is to use char ** as the parameter's type, but that
is not ideal either.
 
B

Ben Bacarisse

Richard Heathfield said:
This one works fine, no warnings. Here's a full program, and a
subsequent make:
int myparse(const char **tokenptrptr, size_t max);

int main(void)
{
char mystring[100] = "Now is the time for all"
" good men to come to the"
" aid of their party";
const char *ptr = &mystring[12];
printf("Current token: %c\n", *ptr);
puts("Parsing");
myparse(&ptr, 10);

I don't think this really addresses the problem. If you already have
a const char * (or const chars to point to as someone else has
suggested) then there is no problem.

I suspect the OP has pointer that is used to modify the string (that
is why it is a char *) and would like to pass it to a function that
promises not to change any. Are you suggesting bracketing the call
with a new declaration of a const char * temporary and an assignment
back to the original pointer just to avoid a cast? This is one place
I'd just use a cast.
printf("Current token: %c\n", *ptr);
return 0;
}

<snip>
 
J

James Kuyper

Nobody said:
Tough luck. The types really are incompatible, and the compiler is obliged
to generate a diagnostic.

A pointer to immutable (const) data and a pointer to mutable data aren't
required to use the same representation.

Actually, they are: "Similarly, pointers to qualified or unqualified
versions of compatible types shall have the same representation and
alignment requirements." 6.2.5p15.

The problem is not that they don't have the same representation, but the
fact that they're not required to be compatible, as you stated in your
first paragraph.
 
J

James Kuyper

Richard said:
Michael Schumacher wrote: said:
Yep (well, maybe), but the question is whether/how it's possible
to declare "tokenptrptr" straight-forward, with no intermediate
steps, such that the two required constraints (error message when
modifying "**tokenptr", and no warning for "myparse (&tokenptr);")
will be satisfied. Your solution sorta works around this, but it
still causes a warning for the function call. Any other ideas?

This one works fine, no warnings. Here's a full program, and a
subsequent make:

#include <stdio.h>

#define DEFAULT_RETURN_CODE 0
#define OVERRUN_AVOIDED 1

int myparse(const char **tokenptrptr, size_t max);

int main(void)
{
char mystring[100] = "Now is the time for all"
" good men to come to the"
" aid of their party";

What he wants (and C does not allow) is to be free to leave out the
'const' on the following declaration:
const char *ptr = &mystring[12];
printf("Current token: %c\n", *ptr);
puts("Parsing");
myparse(&ptr, 10);
printf("Current token: %c\n", *ptr);
return 0;
}

I understand why he wants it; after all, we can pass a char* pointer to
a function declared as taking a const char* argument; it's not
immediately obvious why C couldn't allow passing a char** argument to
function declared as taking a const char**.

6.5.2.2p2: "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".

Thus, we must look to the constrains on assignment:
6.5.16.1p1: "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;"

What's not immediately obvious is that the qualifiers referred to here
are only the top level qualifiers of the pointed-at type. A char* can be
passed to a function expecting a const char*, because "const char" has
all of the qualifiers that "char" has (i.e. none). However, a char**
cannot be passed to a function that expects "const char**", because the
'const' is a part of pointed-at type, rather than qualifying the
pointed-at type directly. Since the pointed-at type, char*, is
incompatible with const char*, assignment of a char** to a const char**
is a constraint violation.

<http://c-faq.com/ansi/constmismatch.html> provides an explanation for
this rule, but it's a tricky explanation, and one I always have trouble
reconstructing from memory.
 
M

Michael Schumacher

Richard said:
This one works fine, no warnings. Here's a full program, and a
subsequent make:

#include <stdio.h>

#define DEFAULT_RETURN_CODE 0
#define OVERRUN_AVOIDED 1

int myparse(const char **tokenptrptr, size_t max);

Yes, I think this is the correct declaration for "tokenptrptr", as
trying, e.g., "(**tokenptr)++;" gives an error. First restriction
solved. But...
int main(void)
{
char mystring[100] = "Now is the time for all"
" good men to come to the"
" aid of their party";
const char *ptr = &mystring[12]; | ^^^^^^^^^^^^^^^
printf("Current token: %c\n", *ptr);
puts("Parsing");
myparse(&ptr, 10);
| ^^^^

In this case, you're passing the address of a "const char *" as a
"const char **" parameter, which is obviously perfectly compatible
and thus doesn't cause the compiler to emit a warning. Omit the
"const" from the "const char *ptr = ..."-declaration, and gcc will
issue a warning for "myparse (&ptr, 10);" -- but that's exactly the
point of my initial posting! [Maybe your "main()" above would like
to manipulate particular characters in "mystring" via "*ptr = 'x';",
in which case you can't declare "ptr" as a "const char *", but that
is not significant for the issue at hand.]

Again, this is not just about getting functional code -- it is about
achieving this without any warnings, work-arounds, intermediate
steps/variables or casts, by just using a straight-forward ISO-C
declaration for a pointer-to-pointer-parameter, where the function
doesn't wish (or isn't allowed to) modify the underlying objects,
but just the actual pointer to it. Is that really an impossible
thing to do in C?
[gcc options]
It may be that you think I'm not pushing the compiler hard enough.
What flags would you have me add?

An additional "-std=c99" wouldn't be bad; however, I don't think it
would help in this particular regard, either. Anyway, thanks a lot
for your (and all the other's) help, I really apprectiate it!


Cheers,
mike
 
T

Tim Rentsch

Michael Schumacher said:
[want to use address of a (char*) as a (const char **)]

Someone already explained that this doesn't work, but no one (as
far as I've seen) has explained why. Consider what happens if it
were legal:

void foo( const char **x ){
static const char xyz[] = "non-writeable";
*x = &xyz[0];
}

char *p;

foo( &p ); // suppose this step were legal
*p = ' '; // legal, but now BAD!

The problem is that, after the call to foo(), p has a pointer to
somewhere that can't be modified, but p doesn't "know" that; hence
p allows storing something into a place that can't have something
stored in it.

If you really want to pass a &(char*) as an argument, rather than
a &(const char*), I think you'll have to resign yourself to using
a (char**) parameter, and arrange the const-ness for code inside
the function using a local variable instead of using the parameter
directly. It's possible to do this in a way that's safe and not
too bad, although it does of course take a few more lines of code,
and storing back the internal value may look a little strange.
 
T

Tim Rentsch

James Kuyper said:
Actually, they are: "Similarly, pointers to qualified or unqualified
versions of compatible types shall have the same representation and
alignment requirements." 6.2.5p15.

The problem is not that they don't have the same representation, but
the fact that they're not required to be compatible, as you stated in
your first paragraph.

The types (char *) and (const char*) have to use the same
representation, but the types (char **) and (const char**)
do not. Presumably the types being talked about in the
previous posting are these, despite the poor choice
of phrasing in the last sentence.
 
M

Michael Schumacher

Tim said:
Michael Schumacher said:
[want to use address of a (char*) as a (const char **)]

Someone already explained that this doesn't work, but no one (as
far as I've seen) has explained why. Consider what happens if it
were legal: [...]

Okay, thanks to your and James' explanations, I can now clearly
see *why* this "desire" is impossible -- I can certainly live with
that outcome, as there are various "work-arounds" available, but
it feels a lot better to exactly *know* why these are necessary
in the first place... :)

I'd like to thank all of you for your quick help!


Cheers,
mike
 
F

Flash Gordon

Michael Schumacher wrote:

w/out using a cast or whatever. I guess the problem boils down
to why "&char *" isn't perfectly compatible to "const char **"
if passed as a parameter to a function. The other way round
(passing "&const char *" as a "char **" parameter) should of
course trigger a warning, as it would allow a function to
potentionally modify a constant value, but that's a different
story.

Ah well, that is a FAQ, if you go to http://c-faq.com/ and have a look
at question 11.10, which explains in detail why the C standard does not
allow this.
 

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,999
Messages
2,570,243
Members
46,836
Latest member
login dogas

Latest Threads

Top