[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.c

Need some info on const char **

DSF

4/26/2011 11:50:00 PM

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
21 Answers

jt

4/27/2011 12:10:00 AM

0

DSF <notavalid@address.here> wrote:
> 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 <http://cdec...) can be quite helpful.

> 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
--
\ Jens Thoms Toerring ___ jt@toerring.de
\__________________________ http://t...

DSF

4/27/2011 5:39:00 AM

0

On 27 Apr 2011 00:10:10 GMT, jt@toerring.de (Jens Thoms Toerring)
wrote:

>DSF <notavalid@address.here> wrote:

>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.


>> 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)...

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

jt

4/27/2011 7:59:00 AM

0

DSF <notavalid@address.here> wrote:
> On 27 Apr 2011 00:10:10 GMT, jt@toerring.de (Jens Thoms Toerring)
>
> >> 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)...

> 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.

> >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.

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.

> >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.

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
--
\ Jens Thoms Toerring ___ jt@toerring.de
\__________________________ http://t...

Ben Bacarisse

4/27/2011 2:41:00 PM

0

jt@toerring.de (Jens Thoms Toerring) writes:

<snip>
> 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.

--
Ben.

lawrence.jones

4/27/2011 6:42:00 PM

0

Ben Bacarisse <ben.usenet@bsb.me.uk> wrote:
>
> 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).
--
Larry Jones

Hello, I'm wondering if you sell kegs of dynamite. -- Calvin

gw7rib

4/27/2011 9:03:00 PM

0

On Apr 27, 6:38 am, DSF <notava...@address.here> wrote:
>   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.

DSF

4/28/2011 12:20:00 AM

0

On 27 Apr 2011 07:58:33 GMT, jt@toerring.de (Jens Thoms Toerring)
wrote:

>DSF <notavalid@address.here> wrote:
>> On 27 Apr 2011 00:10:10 GMT, jt@toerring.de (Jens Thoms Toerring)
>>
>> >> 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)...
>
>> 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.

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

Ike Naar

4/28/2011 6:29:00 AM

0

On 2011-04-28, DSF <notavalid@address.here> wrote:
> On 27 Apr 2011 07:58:33 GMT, jt@toerring.de (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++?

DSF

4/29/2011 5:48:00 AM

0

On Thu, 28 Apr 2011 06:28:45 +0000 (UTC), Ike Naar
<ike@iceland.freeshell.org> wrote:


>>>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

Ike Naar

4/29/2011 9:13:00 AM

0

On 2011-04-29, DSF <notavalid@address.here> wrote:
>>Perhaps you are compiling your code as C++?
> 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/constmis...