Need some info on const char **

D

DSF

Hello.

I have written several functions that take a char ** pointer. Since
these functions do not alter the pointer array or the character
arrays, I feel it's good to label the pointer as const.

I have two concerns:

1. I'm a little rusty on the meaning of const in this case. Does it
indicate that the pointer array will not be altered, the character
arrays will not be altered, or both? What are the usable
permutations?

2. Any call to say,

int foo(const char **ca);
....
int r;
char *c;
char **c_array;
....
r = foo(c_array);

requires a cast

r = foo((const **)c_array);

or I receive a "suspicious pointer conversion" warning.


Is it better to use const and cast each call to foo, or omit the
const? On one hand, identifying ca as const seems the right thing to
do, but on the other hand, requiring the cast allows for bugs.

r = foo((const **)c);

Will compile without a peep from the compiler that something nasty
has just occurred.

What is the proper way to handle this?

DSF
 
J

Jens Thoms Toerring

DSF said:
I have written several functions that take a char ** pointer. Since
these functions do not alter the pointer array or the character
arrays, I feel it's good to label the pointer as const.
I have two concerns:
1. I'm a little rusty on the meaning of const in this case. Does it
indicate that the pointer array will not be altered, the character
arrays will not be altered, or both? What are the usable
permutations?

It depends on where you put the 'const'. E.g. with

void foo( const char * const * bar )

you tell that the function is neither going to change what
the 'bar' points to nor what the result of dereferencing
'bar' points to.

So the question is what you want to be considered as 'const':

a) what the pointer points to *and* what the result of
dereferencing it points to
b) only what the pointer points to
c) only what is pointed to when dereferencing the pointer

So you have

a) const char * const * bar

-> declare 'bar' as pointer to const pointer to const char

b) char * const * bar

-> declare 'bar' as pointer to const pointer to char

c) const char ** bar

-> declare 'bar' as pointer to pointer to const char

Take your pick;-)
2. Any call to say,
int foo(const char **ca);

This declares a function that takes a pointer to a pointer
to const char - you're allowed to change what 'ca' points
to but not what's pointed to by what 'ca' points to (per-
haps an immutable array). I.e. if foo() is callled like
this

const char * a = "Hello";
foo( &a );

then you can do in foo():

void foo( const char **ca )
{
*ca = "world";
}

and then, afterwards in the caller, 'a' will point to the
(immutable) string "world". But you can't do

void foo( const char ** ca )
{
**ca = 'X';
}

to have what 'a' points to in the caller to be changed to
"Xello".

If in doubt what the "gibberish" means (i.e. where to put
the 'const') the 'cdecl' program (which can also to be used
via said:
int r;
char *c;
char **c_array;
...
r = foo(c_array);
requires a cast
r = foo((const **)c_array);

I don't think the compiler will really like this (it could
be upset about the missing type and maybe mumble something
about using 'int' as a default)...
Is it better to use const and cast each call to foo, or omit the
const? On one hand, identifying ca as const seems the right thing to
do, but on the other hand, requiring the cast allows for bugs.

Specify exactly what your function expects. Then there's no
room left for mistakes, neither by you nor by the compiler.
Then avoid casts, they just tell the compiler "that func-
tion wants something different from what I'm passing it, but
I know better, for some reasons you're too stupid to under-
stand, what it will get is what it expects. Just trust me
and don't bother me with what you may consider to be helpful
but actually are just annoying warnings."

If a function expects something it's to treat as constant but
gets passed something non-constant than there's no problem at
all. But when a function expects something it can change but
the compiler is asked to pass it something that is declared
as constant than there's typically a problem. Sure, you can
dismiss the compiler's concerns with a cast, but when you do
that you'd better have a real good reason.

Regards, Jens
 
D

DSF

It depends on where you put the 'const'. E.g. with

void foo( const char * const * bar )

you tell that the function is neither going to change what
the 'bar' points to nor what the result of dereferencing
'bar' points to.

Using "const char * const *" is what I was looking for. Neither the
pointer array or the strings pointed to are to be altered. I no
longer get the warning and no cast is required.

So the question is what you want to be considered as 'const':
{Your post rearranged a bit}
To see if I've got this straight. Assuming in this case a pointer
to an array of pointers, each of which points to an array of char,i.e.
a string array:
a) what the pointer points to *and* what the result of
dereferencing it points to
a) const char * const * bar
Which would be both the array of pointers itself and the array of
strings the pointers point to.
b) only what the pointer points to
b) char * const * bar
The array of pointers may not be changed, but the strings pointed to
may.
c) only what is pointed to when dereferencing the pointer
c) const char ** bar
The array of pointers may be changed, but not the strings pointed
to.

I don't think the compiler will really like this (it could
be upset about the missing type and maybe mumble something
about using 'int' as a default)...

What missing type? The above line is what's necessary for the
compiler to not complain. (Keeping in mind your earlier answer gave
me the solution, I'm just curious what you mean.)
Does this have anything to do with the fact that you snipped this:
int foo(const char **ca);
....
from above int r; (above)?

Specify exactly what your function expects. Then there's no
room left for mistakes, neither by you nor by the compiler.

I've thought of it as not what my function expects, but rather a
promise as to what my function won't do. The declaration
int foo(const char * const *ca);
promises that foo will not change anything through ca. Not the array
pointers, nor the strings themselves.

If a function expects something it's to treat as constant but
gets passed something non-constant than there's no problem at
all.

That's my confusion. The compiler complains when the function int
foo(const char **ca) is passed a non-const pointer, char **c_array. I
thought you could always go from non-const to const without any
problems.

DSF
 
J

Jens Thoms Toerring

DSF said:
On 27 Apr 2011 00:10:10 GMT, (e-mail address removed) (Jens Thoms Toerring)
What missing type? The above line is what's necessary for the
compiler to not complain.

The '(const **)' bit is missing the type to cast to, it
just says 'cast to pointer to const pointer of something
of uspecified type' and I guess you just missed the 'char'
bit in there. At least my compiler doesn't find that just
right and tells me it's using 'int' as the default type.
I've thought of it as not what my function expects, but rather a
promise as to what my function won't do. The declaration
int foo(const char * const *ca);
promises that foo will not change anything through ca. Not the array
pointers, nor the strings themselves.

Both are a bit of the truth. The 'const' makes a promise
that the function will refrain from changing something
but it also may out some obligation on the types the
caller can pass it, which is just what's happening in
your case, see below.
That's my confusion. The compiler complains when the function int
foo(const char **ca) is passed a non-const pointer, char **c_array. I
thought you could always go from non-const to const without any
problems.

There's the following problem in your special case:
When you

int foo( const char ** p );

char **a;
f( a );

then the caller expects that it can change the array 'a' is
pointing to. But in foo() you can change what it points to
and it could be made to point to an immutable array. So
for example you could be trying to do

void foo( const ** p ) { *p = "hello"; }

int main( )
{
char *a;
foo( &a );
a[ 1 ] = 'x';
return 0;
}

So you make 'a' indirectly point to an (of course im-
mutable) string literal in foo() even though it's not
declared as a pointer to const char. And now there's
no protection anymore against changing what 'a' points
to in main(). But when you now try to modify it things
can go badly wrong. That should be excluded by using

void foo( const char * const * p );

since with that what 'p' points to also can't modified
in foo() (although my compiler still complains and I
haven't yet figured out why - or what what nasty things
I still could do in foo() ;-)

Regards, Jens
 
B

Ben Bacarisse

(e-mail address removed) (Jens Thoms Toerring) writes:

There's the following problem in your special case:
When you

int foo( const char ** p );

char **a;
f( a );

then the caller expects that it can change the array 'a' is
pointing to. But in foo() you can change what it points to
and it could be made to point to an immutable array. So
for example you could be trying to do

void foo( const ** p ) { *p = "hello"; }

(missing type: const char **p presumably)
int main( )
{
char *a;
foo( &a );
a[ 1 ] = 'x';
return 0;
}

So you make 'a' indirectly point to an (of course im-
mutable) string literal in foo() even though it's not
declared as a pointer to const char. And now there's
no protection anymore against changing what 'a' points
to in main(). But when you now try to modify it things
can go badly wrong. That should be excluded by using

void foo( const char * const * p );

since with that what 'p' points to also can't modified
in foo() (although my compiler still complains and I
haven't yet figured out why

Parameter passing is defined in terms of assignment:

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

Simple assignment requires that one of 6 conditions apply. Five have
nothing to do with this situation so what is needed is that:

"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;" (6.5.16.1 p1)

The type of p is a pointer to a const-qualified version of const char *
while you are passing a pointer to a char *. These two types are not
compatible:

"For two pointer types to be compatible, both shall be identically
qualified and both shall be pointers to compatible types." (6.7.5.1
p2)

I don't think passing a char ** to a function that wants a const char
*const * can do any harm. It's just that C choose a simple rule that
happens to mean that a cast is required in this case. C's rules lets you
have more qualifiers on both the parameter itself (foo(const int);) and
on the pointed-to type (foo(const int *);) but you can't add qualifiers
to the type pointed to by the pointed-to type (I hope I've written that
correctly). C++ permits the call, by the way, because it has more
complex rules about const types. It seems a shame that C requires a
cast in these cases but I image that retrofitting C++'s rules to C was
not considered to be worth the effort.
- or what what nasty things
I still could do in foo() ;-)

None, I think in this case.
 
L

lawrence.jones

Ben Bacarisse said:
C++ permits the call, by the way, because it has more
complex rules about const types. It seems a shame that C requires a
cast in these cases but I image that retrofitting C++'s rules to C was
not considered to be worth the effort.

More a case of no one stepping up and volunteering to do the work (which
is non-trivial).
 
P

Paul N

  That's my confusion.  The compiler complains when the function int
foo(const char **ca) is passed a non-const pointer, char **c_array.  I
thought you could always go from non-const to const without any
problems.

Ah! Only with one level of pointer. If it's two levels (and you don't
use const in the middle as well) you get problems, for example:

#include <stdio.h>

int main(void) {
const char **cpp;
char **pp;
const char *cp;
char *p;
char buff[] = "abc";

cp = buff; // OK
pp = &p; // OK
cpp = pp; // not legal?
*cpp = cp; // OK
*p = 'x'; // alters buff[0], which only had a const pointer pointing
to it

puts(buff);
return 0;
}

Though strangely enough, my compiler did accept this without any
errors or warnings. But it made the expected complaint when I told it
to compile in C++ mode.
 
D

DSF

The '(const **)' bit is missing the type to cast to, it
just says 'cast to pointer to const pointer of something
of uspecified type' and I guess you just missed the 'char'
bit in there. At least my compiler doesn't find that just
right and tells me it's using 'int' as the default type.

Yup! My typo. It was supposed to be
r = foo((const char **)c_array);
And I missed it twice!

{snipped}
So you make 'a' indirectly point to an (of course im-
mutable) string literal in foo() even though it's not
declared as a pointer to const char. And now there's
no protection anymore against changing what 'a' points
to in main(). But when you now try to modify it things
can go badly wrong. That should be excluded by using

void foo( const char * const * p );

since with that what 'p' points to also can't modified
in foo() (although my compiler still complains and I
haven't yet figured out why - or what what nasty things
I still could do in foo() ;-)

Mine is content and doesn't give a peep with:

char *StringArrayToMultistring(const char * const *stringarray, char
*multistring);
....
char **s_array, ms[200];
....
(Allocates and fills s_array)
....
StringArrayToMultistring(s_array, ms);

To use a real world example.

Here's the code for StringArrayToMultistring, for the heck of it. It
takes an array of character arrays and copies them in order, complete
with terminators, into a single character array terminated by an empty
string (a single 0).

char *StringArrayToMultistring(const char * const *stringarray, char
*multistring)
{
size_t sai, msi, asl;

sai = msi = 0;
while(stringarray[sai] != NULL)
{
asl = strlen(stringarray[sai]) + 1;
memcpy(multistring + msi, stringarray[sai], asl);
msi += asl;
sai++;
}
multistring[msi] = 0;

return multistring;
}

DSF
 
I

Ike Naar

On 27 Apr 2011 07:58:33 GMT, (e-mail address removed) (Jens Thoms Toerring) wrote:
{snipped}
So you make 'a' indirectly point to an (of course im-
mutable) string literal in foo() even though it's not
declared as a pointer to const char. And now there's
no protection anymore against changing what 'a' points
to in main(). But when you now try to modify it things
can go badly wrong. That should be excluded by using

void foo( const char * const * p );

since with that what 'p' points to also can't modified
in foo() (although my compiler still complains and I
haven't yet figured out why - or what what nasty things
I still could do in foo() ;-)

Mine is content and doesn't give a peep with:

char *StringArrayToMultistring(const char * const *stringarray, char
*multistring);
...
char **s_array, ms[200];
...
(Allocates and fills s_array)
...
StringArrayToMultistring(s_array, ms);

To use a real world example.

Perhaps you are compiling your code as C++?
 
D

DSF

since with that what 'p' points to also can't modified
in foo() (although my compiler still complains and I
haven't yet figured out why - or what what nasty things
I still could do in foo() ;-)

Mine is content and doesn't give a peep with:

char *StringArrayToMultistring(const char * const *stringarray, char
*multistring);
...
char **s_array, ms[200];
...
(Allocates and fills s_array)
...
StringArrayToMultistring(s_array, ms);

To use a real world example.

Perhaps you are compiling your code as C++?

No. May I ask what led to that question?

I double checked. Definitely C. I checked the project node just in
case somehow it was set to override the default of C for a *.c file.
It's set to C.

DSF
 
I

Ike Naar

No. May I ask what led to that question?

The remark that the compiler did not complain. Consider

/* ex.c */
void foo(char const * const *s);
void bar(char **s) { foo(s); }

In C, the types in the call to foo() are incompatible and the compiler
must produce a diagnostic. Using gcc 4.1.2:

$ cc -ansi -Wall -pedantic -c ex.c
ex.c:2: warning: passing argument 1 of 'foo' from incompatible pointer type

In C++ the types are compatible and no diagnostic is produced:

$ c++ -Wall -pedantic -c ex.c

There's an item about this issue in the C FAQ:

http://c-faq.com/ansi/constmismatch.html
 
B

Ben Bacarisse

Ike Naar said:
The remark that the compiler did not complain. Consider

/* ex.c */
void foo(char const * const *s);
void bar(char **s) { foo(s); }

In C, the types in the call to foo() are incompatible and the compiler
must produce a diagnostic. Using gcc 4.1.2:

$ cc -ansi -Wall -pedantic -c ex.c
ex.c:2: warning: passing argument 1 of 'foo' from incompatible pointer type

Despite what the compiler says, calls with incompatible types (including
incompatible pointer types) are often permitted. Not in this case, of
course, but incompatibility cannot, of itself, be the reason for the
complaint. What we need is a new term: "assignment compatible" or maybe
simply "assignable". The problem is that a char ** is not assignable to
a const *const * lvalue (since the validity of a function call is
defined in terms of assignment).

<snip>
 
D

DSF

Despite what the compiler says, calls with incompatible types (including
incompatible pointer types) are often permitted. Not in this case, of
course, but incompatibility cannot, of itself, be the reason for the
complaint. What we need is a new term: "assignment compatible" or maybe
simply "assignable". The problem is that a char ** is not assignable to
a const *const * lvalue (since the validity of a function call is
defined in terms of assignment).

In my case, would it be best just to drop the const and use:
char *StringArrayToMultistring(char **stringarray, char
*multistring); ?

StringArrayToMultistring will not affect any level of **stringarray
in either case, so it seems the const(s) will create problems that do
not need to exist.

DSF
 
I

Ike Naar

In my case, would it be best just to drop the const and use:
char *StringArrayToMultistring(char **stringarray, char
*multistring); ?

StringArrayToMultistring will not affect any level of **stringarray
in either case, so it seems the const(s) will create problems that do
not need to exist.

That's a possibility; or use a cast:

void foo(char const * const *);

/* ... */
{
char **stringtable;
/* ... */
foo((char const * const *) stringtable);
/* ... */
}
 
B

Ben Bacarisse

I like const checking enough that I prefer to live with a cast or two
rather than then ditching it altogether.
That's a possibility; or use a cast:

void foo(char const * const *);

/* ... */
{
char **stringtable;
/* ... */
foo((char const * const *) stringtable);
/* ... */
}

I prefer to have the cast add only "just enough" constness:

foo((char const **)stringtable);

Maybe stringtable can, one day, have its type changed and I have a
reminder of what it needs to be to remove the cast.
 
D

DSF

The remark that the compiler did not complain. Consider

/* ex.c */
void foo(char const * const *s);
void bar(char **s) { foo(s); }

In C, the types in the call to foo() are incompatible and the compiler
must produce a diagnostic. Using gcc 4.1.2:

$ cc -ansi -Wall -pedantic -c ex.c
ex.c:2: warning: passing argument 1 of 'foo' from incompatible pointer type

In C++ the types are compatible and no diagnostic is produced:

$ c++ -Wall -pedantic -c ex.c

There's an item about this issue in the C FAQ:

http://c-faq.com/ansi/constmismatch.html

I forgot to mention that I copied and pasted your above code into my
compiler, turned on all warnings, and compiled it. It came up 0
warnings, 0 errors. I even turned on strict ANSI compliance (which I
normally have to leave off if I want to compile *anything at all*) and
still 0, 0!

So I can assume that at least in this instance, my compiler is not
ANSI compliant. :(

DSF
 
D

DSF

I like const checking enough that I prefer to live with a cast or two
rather than then ditching it altogether.


I prefer to have the cast add only "just enough" constness:

foo((char const **)stringtable);

Makes sense, but I have no way of even checking if the above is
"just enough." My compiler produces no warnings or errors with no
casting at all. That's one of the reasons I'm considering dropping
the const altogether, another is below.
Maybe stringtable can, one day, have its type changed and I have a
reminder of what it needs to be to remove the cast.

I assume by this you mean "stringtable" in the above example
becoming a const itself. This is very unlikely to ever happen. As I
said before, I use const char * const * only as a promise that the
functions that use it will not modify the pointer array or the string
arrays. Other functions can and freely do modify the array.

I consider the "promise" part of const to be important. if I'm
using a function that takes a non-const pointer (or pointer to
pointer, etc.) to data that I do not want modified, I make a copy of
the data and use a pointer to the copy. Even if I'm *certain* the
function doesn't modify the data: It won't today, but it might
tomorrow.

So it's a decision between making a promise of data integrity vs.
the added "danger" of someday hiding the wrong pointer behind a cast
and the merry hi-jinks sure to ensue tracking down the resulting bug!
:eek:)

DSF
 
B

Ben Bacarisse

DSF said:
Makes sense, but I have no way of even checking if the above is
"just enough." My compiler produces no warnings or errors with no
casting at all. That's one of the reasons I'm considering dropping
the const altogether, another is below.

Are you sure you are not invoking the compiler in C++ mode? If it is in C
mode and (as you report elsewhere) your get no messages from the highest
warning level, you could report a bug to the authors. What is the
compiler?

<snip>
 
E

Edward A. Falk

I believe this declares foo() as accepting a pointer to
a const pointer to const char. Meaning that you can modify the
pointer s, but not the pointer pointed to by s, nor the
character that the pointed-to pointer points to.

void foo(char const * const *s)
{
**s = 'a'; /* illegal */
*s = NULL; /* illegal */
s = NULL; /* legal */
}

The declaration is in effect a promise that foo will not
modify the pointer that is handed to it, nor try to modify
whatever that pointer points to.

I would have thought that passing something with fewer restrictions
would have been acceptable, but gcc objects, insisting that I pass a
pointer to a pointer to const char.

I didn't expect that.
 
I

Ike Naar

I forgot to mention that I copied and pasted your above code into my
compiler, turned on all warnings, and compiled it. It came up 0
warnings, 0 errors. I even turned on strict ANSI compliance (which I
normally have to leave off if I want to compile *anything at all*) and
still 0, 0!

So I can assume that at least in this instance, my compiler is not
ANSI compliant. :(

Just out of curiosity: what compiler are you using?
 

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,769
Messages
2,569,582
Members
45,070
Latest member
BiogenixGummies

Latest Threads

Top