I know implicit conversion from char **
to const char **
cannot be done and why, and that the conversion to char *const *
works. See bottom for links to explanation on that.
It all makes sense apart from one particular thing. So I have the following code:
#include <stdio.h>
void
print(const char *const*param)
{
printf("%s\n", param[0]);
}
int
main(int argc, char **argv)
{
print(argv);
return 0;
}
If I compile this as a C++ code, it compiles quite fine. However, if the same code is compiled as a C code only, I get an error (well, a warning, but let's suppose -Werror
, i.e. treat warnings as errors).
gcc:
test.c: In function ‘main’:
test.c:12:11: warning: passing argument 1 of ‘print’ from incompatible pointer type [-Wincompatible-pointer-types]
print(argv);
^
test.c:4:1: note: expected ‘const char * const*’ but argument is of type ‘char **’
print(const char *const*param)
^
clang:
test.c:12:11: warning: passing 'char **' to parameter of type 'const char *const *' discards qualifiers in nested pointer types [-Wincompatible-pointer-types-discards-qualifiers]
print(argv);
^~~~
test.c:4:25: note: passing argument to parameter 'param' here
print(const char *const*param)
^
Both behaviours are standard-independent and also compiler-independent. I tried various standards with both gcc
and clang
.
There are two reasons for this inquiry. Firstly, I want to understand whether there is a difference and, secondly, I have a function that does nothing with any layer of the pointers and I need it to be able to work with const char **
as well as char *const *
and char **
. Explicitly casting each call is not maintainable. And I have no idea how should the function prototype look like.
This is the question that started my curiosity: Implicit conversion from char** to const char**
And here is another nice explanation for the char ** => const char**
problem:
http://c-faq.com/ansi/constmismatch.html
If the links are confusing related to this question, feel free to edit them out.
const char* const says that the pointer can point to a constant char and value of int pointed by this pointer cannot be changed. And we cannot change the value of pointer as well it is now constant and it cannot point to another constant char.
The difference is that const char * is a pointer to a const char , while char * const is a constant pointer to a char . The first, the value being pointed to can't be changed but the pointer can be. The second, the value being pointed at can change but the pointer can't (similar to a reference).
Simple: "char *name" name is a pointer to char, i.e. both can be change here. "const char *name" name is a pointer to const char i.e. pointer can change but not char.
If you don't have the choice, using const char* gives a guarantee to the user that you won't change his data especially if it was a string literal where modifying one is undefined behavior. Show activity on this post. By using const you're promising your user that you won't change the string being passed in.
C and C++ are different in this respect. I don't have an answer to why C++ is more generous, other than that the C++ behaviour seems to me to be correct.
C simply doesn't allow indirect const
conversion. That is a conservative, easy-to-implement restriction, with the unfortunate consequence that you cannot provide char*[]
to a function expecting char const* const*
. The restriction is in §6.3.2.3, paragraph 2, and it is simply not recursive:
For any qualifier
q
, a pointer to a non-q
-qualified type may be converted to a pointer to theq
-qualified version of the type; the values stored in the original and converted pointers shall compare equal.
C++ allows conversions according to a somewhat complex formulation in §4.4 [conv.qual], paragraph 3. It is permitted to convert
T cvn Pn-1cvn-1 … P1cv1 P0cv0
⇒
T cv'n Pn-1cv'n-1 … P1cv'1 P0cv'0
(where T
is a type; P1…Pn
are pointer/array type constructors, and each cv0…cvn
is some possibly empty subset of const
and volatile
)
provided that:
For every k > 0
, cvk
is a subset of cv'k
(so you can't remove a const
or a volatile
), and
If cvk
and cv'k
differ for some k > 0
, all the following cv'i>k
include const
.
In the actual standard, that expression is reversed; I put it in the order of declaration, whereas in the standard it is in order of application of the pointer/array constructors. I didn't change the direction of the numbering, though, which is why they are numbered right to left. I also left out some details -- for example, it's not strictly necessary for the two T
s to be identical -- but I think it gives an idea of the intention.
The explanation for the first restriction is reasonably obvious. The second restriction prevents the problem described in the C FAQ, where a const
pointer might be stored into a non-const
pointer object, and then subsequently used to mutate the const
object it points to.
The bottom line is that in C++, your prototype const char *const * param
will work with arguments of type char**
, const char**
, or even char*const*
, but in C only the last one will work without warning, and it is the least useful. The only workaround I know of (other than switching to C++) is to ignore the warning.
For what it's worth, there is a note in the Rationale section of the Posix specification of the exec*
interfaces about the problem this causes for these prototypes, and the workaround selected by Posix, which is to use char*[]
as the prototype and textually note that these are constant: (emphasis added)
The statement about
argv[]
andenvp[]
being constants is included to make explicit to future writers of language bindings that these objects are completely constant. Due to a limitation of the ISO C standard, it is not possible to state that idea in standard C. Specifying two levels ofconst
-qualification for theargv[]
andenvp[]
parameters for the exec functions may seem to be the natural choice, given that these functions do not modify either the array of pointers or the characters to which the function points, but this would disallow existing correct code. Instead, only the array of pointers is noted as constant.
There's a useful compatibility chart following that paragraph, which I didn't quote because of the formatting limitations of this site.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With