Reputation: 2031
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
static int cmpstringp(const void *p1, const void *p2)
{
/* The actual arguments to this function are "pointers to
pointers to char", but strcmp(3) arguments are "pointers
to char", hence the following cast plus dereference */
return strcmp(* (char * const *) p1, * (char * const *) p2);
}
int main(int argc, char *argv[])
{
int j;
assert(argc > 1);
qsort(&argv[1], argc - 1, sizeof(argv[1]), cmpstringp);
for (j = 1; j < argc; j++)
puts(argv[j]);
exit(EXIT_SUCCESS);
}
I'm confused with this part:
return strcmp(* (char * const *) p1, * (char * const *) p2);
Why did they do this? Why did they NOT do this: (const char**)
or (const char * const*)
? Don't we get a pointer to a const char if we dereference once for (const char**)
? Dereferencing the second one, don't we get a const pointer that points at a const char. Both of these seem to what strcmp()
asks for: two pointers who point at const chars. What the man page seems to give us const pointers pointing at non-const things, which doesn't seem to be what strcmp()
's declaration is asking for. Even if legal, it doesn't seem like a good idea to give a function something that doesn't fit its parameters. Am I missing something?
Lastly, why does the following not generate at least an error warning:
auto const char * const ptr3 = *(const char **) ptr1; //where ptr1 is
of the form int foo(const void * ptr).
Dereferencing ptr1
once gives us a pointer to a const char, but is not itself const. However, ptr3
is const. So why doesn't the compiler generate a warning? Am I missing something, or is there a reason it shouldn't be generating a warning?
Upvotes: 1
Views: 902
Reputation: 213358
Last question first:
This is fine:
const void *ptr = ...;
const char * const ptr3 = *(const char **) ptr1;
^^^^^^^^^^^^ ~~~~~ ^^^^^^^^^^^^
| |
+----------------------------+
Here, I can simplify it with a typedef:
typedef const char * string;
const void *ptr = ...;
const string ptr3 = *(string *) ptr;
~~~~~ ^^^^^^ ^^^^^^
| |
+----------------+
The const
in the local variable (the one underlined with ~~~~~
) is not really necessary: it specifies that the local variable ptr3
is const
, not that the data it points to is const.
Next question: Why (or why not) not *(const char * const *) ptr1
?
Well, the type of *(const char * const *) ptr1
is const char * const
, or const string
if you are using the typedef. But it's only used as an rvalue. There is no difference between a const and non-const rvalue. For example, look at the following code:
void *ptr = ...;
int x = *(const int *) ptr;
int y = *(int *) ptr;
Obviously, x
and y
get the same value. So there is no real semantic benefit to adding const
here, it is just extra typing.
But: some compilers will give a warning...
const void *ptr = ...;
string ptr2 = *(string *) ptr;
Since you are casting a pointer to const void
to a pointer to non-const string
, some compilers may give a warning that you are discarding qualifiers. C++ compilers shouldn't even let such code pass, since they'll demand a const_cast
to convert from const void *
to string *
(a.k.a const char *const *
).
However: the reverse conversion is fine. It is fine to pass pointers to non-const objects to strcmp
. The fact that strcmp
takes pointers to const data indicates that strcmp
itself does not modify the data.
There are many ways to write the function that are fine. Here are some examples.
return strcmp(*(char **) p1, *(char **) p2);
return strcmp(*(const char **) p1, *(const char **) p2);
return strcmp(*(char * const *) p1, *(char * const *) p2);
return strcmp(*(const char * const *) p1, *(const char * const *) p2);
// The following are not technically portable, but are portable in practice
return strcmp(*(void **) p1, *(void **) p2);
return strcmp(*(const void **) p1, *(const void **) p2);
return strcmp(*(void * const *) p1, *(void * const *) p2);
return strcmp(*(const void * const *) p1, *(const void * const *) p2);
Since you are casting variables anyway, the compiler will let a lot of different potential errors slide, but some compilers may give warnings. Use whichever version suits your coding style.
Final tip: The auto
keyword is obsolete. It doesn't really mean anything these days -- it's the default storage class specifier. Don't use auto
.
Upvotes: 2
Reputation: 308206
First part of your question, I think there's some flexibility on where you place the const
, so const char **
and char * const *
are the same.
Second part of your question, any pointer can be assigned to a const
pointer. It's going the other way that generates an error. Your explicit cast removed any knowledge the compiler had about the pointer's original type.
Upvotes: 0