What is char * const *? - c

So it seems like it means a pointer to a constant pointer to char. That is it points to a char * const, so far so good.
What gets me confused is where and how I saw it used. I was looking at the man page for qsort and the example does the following to convert the pointers to elements of a char ** (an array of strings), (pointers to elements seen as const void *) to normal char pointers feedable to strcmp:
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);
}
My question is, why is there a cast to char * const *? Why isn't it just a const char ** (because eventually we want to send a const char * to strcmp)?

char * const * indeed means a pointer to a constant pointer to chars. The reason this cast is performed in the code in the question is the following:
p1 and p2 are (non-const) pointers to a constant location. Let's assume the type of this location is const T.
Now we want to cast p1 and p2 to their real types. We know that each element of the array is a char *, therefore T = char *. That is const T is a constant pointer to char, which is written as char * const.
Since p1 and p2 are pointers to the elements of the array, they are of type const T *, which is char * const *.
Since the function merely calls strcmp, in truth it wouldn't have made any difference if the parameters were cast to char ** or const char ** or const char * const * or whatever.

When a function declares that it takes a pointer-to-const elements (e.g. strcmp()) it means that the function promises not to mody the elements via the pointer, it does not mean that the parameters passed to that function must be pointers-to-const themselves.
Remember: the const modifier is a contract term, basically meaning that the declaring function promises not to modify the element the const modifies. Conversion in the direction of non-const -> const therefore is usually OK.

Related

Trying to print a string in a qsort function

I'm having some trouble passing a valid value to the qsort function but can't quite figure out what's going wrong with it. Here is what I have so far:
int main(void)
{
char* strings[4] = {"Onus", "deacon", "Alex", "zebra"};
printf("%zu\n", sizeof(strings));
qsort(strings, 4, 8, scmp);
}
int scmp(const void *p1, const void *p2)
{
printf("%s\n", (char*) p1);
return 0;
// ignore return value -- I'm just looking to print the string.
}
It just seems to print gibberish when I do this. Is this because qsort expects a value a pointer to a value and I'm passing it a pointer to a (char) pointer? What would be the correct way to reference it then?
It seems instead it should be: printf("%s\n", *(char* const*) p1); ? This is from some trial-and-error, though not sure why that works -- i.e., the *(char**).
For example, for passing an int I can do:
const int *v1 = p1;
But then a char* needs to be:
const char *s1 = *(char* const *) p1;
Why not just const char *s1 = (char*) p1; ?
strings is an array of pointers to char. The comparison function for qsort gets passed pointers to the two elements of the array that should be compared. Since the elements of the array are pointers to char, the arguments p1, p2 to scmp are pointers to (const) pointers to char, and should be cast to char ** (or rather char * const *).
What needs to be passed to printf is the pointer to char itself, so you have to dereference the argument to get that pointer. If you wanted to look at the individual characters of the string, you'd have to dereference again. There are two *s in the name of the type, so you have to apply * two times to get back to the underlying primitive type.
printf("The first character of the string is %c\n", **(char * const *)p1);

Question concerning the comparison function passed as a parameter to the qsort() function

I'm taking a specialization on Coursera and in a lesson it explains the qsort() function that sorts a given array:
void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
where we should provide qsort() with four parameters - the array to sort, number of elements in the array, size of each element of the array, and a pointer to a function (compar) which takes two const void *s and returns an int. The lesson says that we need to write the compar function to be compatible with the qsort function, so if we would like to compare two strings the function should look like:
int compareStrings(const void * s1vp, const void * s2vp) {
// first const: s1vp actually points at (const char *)
// second const: cannot change *s1vp (is a const void *)
const char * const * s1ptr = s1vp;
const char * const * s2ptr = s2vp;
return strcmp(*s1ptr, *s2ptr);
}
void sortStringArray(const char ** array, size_t nelements) {
qsort(array, nelements, sizeof(const char *), compareStrings);
}
It says: Note that the pointers passed in are pointers to the elements in the array (that is, they point at the boxes in the array), even though those elements are themselves pointers (since they are strings). When we convert them from void *s, we must take care to convert them to the correct type—here, const char * const *—and use them appropriately, or our function will be broken in some way. For example, consider the following broken code:
// BROKEN DO NOT DO THIS!
int compareStrings(const void * s1vp, const void * s2vp) {
const char * s1 = s1vp;
const char * s2 = s2vp;
return strcmp(s1, s2);
}
The thing that I can't really get is why didn't we consider s1vp and s2vp as pointers to pointers? I mean, since the arguments passed to the function compareStrings are addresses of pointers pointing to strings (address of pointer), shouldn't we have declared s1vp and s2vp as int compareStrings(const void ** s1vp, const void ** s2vp) since they are receiving addresses of pointers?
In other words, I'm passing, for example, the address of the first element of the array of strings, which is actually a pointer, to s1vp. So now s1vp is receiving address of pointer not a variable, so We should declare it as pointer to pointer, right? It gives me warning when I try to do so...
A void * can point to any datatype. The fact that the datatype in question is also a pointer doesn't change things.
Also, you can't change the signature of the comparison function, otherwise it would be incompatible with what qsort is expecting and can lead to undefined behavior.

Why does calling a function that accepts a (const char * const **) with a char *** raise a -Wcast-qual flag?

If we have void foo(const char * const ** bar); and we call it with char *** bar = malloc(...); such as foo((const char * const **) bar); It returns:
error: to be safe all intermediate pointers in cast from ‘char ***’ to ‘const char * const**’ must be ‘const’ qualified [-Werror=cast-qual]
Trying to pass it in without casting results in a type warning, but passing a char * into a const char * works, I can also pass a char *** into a char ** const * function parameter, but I want more read-only qualifier than just a single level.
The intent is that the function assumes the entire pointer data and pointer being passed in are read-only, such that the pointer won't be written to in any way.
I understand that the flag is an extra one, but it's defined as:
-Wcast-qual
Warn whenever a pointer is cast so as to remove a type qualifier from the target type. For example, warn if a const char * is cast to an ordinary char *.
As far as I can tell, no type qualifier is being removed, rather we're adding type qualifiers to let the user know that the function doesn't change any data.
Why is the previously mentioned code triggering the extra warning -Wcast-qual? Is there a better way of going about passing a completely malloc'd triple pointer as a read-only function?
Why does passing a char* to a function that expects a char const* work?
It's simple to understand that. You are not able to modify the string in the called function. That's OK.
Why does passing a char** to a function that expects a char const** not work?
This sounds non-intuitive but there is a reason. Take this analogy with an int.
const int a = 10;
void foo(int const ** ap)
{
*ap = &a;
}
void bar()
{
int * p = NULL;
foo(&p);
*p = 20;
}
If you were allowed to use that, you are able to modify a, which is const, in bar indirectly.
That's why &p, whose type is int**, is not allowed to be cast to int const** implicitly.
TL;DR:
const char * const ** -> const char * const * const *
To finish the question, answered in the comments by /users/2410359/chux (asked them to make a post, but they haven't). The solution was to use a const char * const * const * which does not trigger the -Wcast-qual flag. Basically what I had was not true read-only qualifiers, I needed another level.

Use of pointer to const

From the man page of qsort, in an example of sorting strings:
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);
}
Why is it necessary to have char * const * in the arguments to strcmp()? Isn't char * enough?
strcmp is declared as
int strcmp(
const char *string1,
const char *string2
);
This properly expresses the function's interface contract - which is that strcmp will not modify its input data - and allows the compiler to optimize inside the function (assuming it were not part of the CRT, and likely in assembler already).
const void* p1 says that whatever p1 points at is not changed by this function. If you did
char** p1_copy = (char**) p1;
that would be a setup to potentially break that promise, because you could then do
*p1_copy = "Something else";
So a cast from const void* to char** is said to "cast away const". Legal, but some compilers will warn if you use a cast to both cast away const and otherwise change the type at once.
The cast that doesn't break the promise of the const void* p1 declaration is the one used:
char* const* p1_arg = (char* const*) p1;
Now *p1_arg, the thing p1 points to, can't be changed just like we said. You could change the characters in it though:
*p1_arg[0] = 'x';
The function declaration never said anything about them, and you say you know them to originally be non-const chars. So it's allowable, even though the function doesn't actually do any such thing.
Then you dereference that (as an rvalue) to get a char*. That can legally be passed as the const char* argument to strcmp by automatic const promotion.
Technically, if you wanted to get rid of the consts, the cast would be to char **, not char *. The const is left in the cast because the arguments to cmpstringp are also const.
A comparison function passed to qsort has no business modifying the items it's comparing.
This is why the general case of qsort looks like:
void qsort(void *base, size_t nmemb, size_t size, int(*compar)(const void *, const void *));

Dynamic array of structs and function call f(const struct_type *const data[])

The following code warns about incompatible type. What is the proper way to solve this code?
thanks
typedef struct a_struct struct_type;
void f(const struct_type *const data[], unsigned n);
void test(unsigned n)
{
struct_type **v;
assert(n);
v = malloc(n * sizeof(struct_type *));
/* fill v with new struct_type (with a malloc per entry) */
f(v, n); <- WARN on v
}
The reason the compiler is complaining is the first const in f's declaration.
Try using
void f(struct_type *const data[], unsigned n);
/*...*/
f( v, n );
and you won't get the same warning. Alternatively, you could cast v when you call f
void f(const struct_type *const data[], unsigned n);
/*...*/
f( (const struct_type * const *) v, n );
This is a little counterintuitive, but in C, you can't pass a pointer-to-pointer-to-nonconst for a pointer-to-pointer-to-const. They made a special exception to allow you to pass a pointer-to-nonconst for a pointer-to-const.
Here's a FAQ question "Why can't I pass a char ** to a function which expects a const char **?":
You can use a pointer-to-T (for any type T) where a pointer-to-const-T is expected. However, the rule (an explicit exception) which permits slight mismatches in qualified pointer types is not applied recursively, but only at the top level. (const char ** is pointer-to-pointer-to-const-char, and the exception therefore does not apply.)
The reason that you cannot assign a char ** value to a const char ** pointer is somewhat obscure. Given that the const qualifier exists at all, the compiler would like to help you keep your promises not to modify const values. That's why you can assign a char * to a const char *, but not the other way around: it's clearly safe to add const-ness to a simple pointer, but it would be dangerous to take it away. However, suppose you performed the following more complicated series of assignments:
const char c = 'x'; /* 1 */
char *p1; /* 2 */
const char **p2 = &p1; /* 3 */
*p2 = &c; /* 4 */
*p1 = 'X'; /* 5 */
In line 3, we assign a char ** to a const char **. (The compiler should complain.) In line 4, we assign a const char * to a const char *; this is clearly legal. In line 5, we modify what a char * points to--this is supposed to be legal. However, p1 ends up pointing to c, which is const. This came about in line 4, because *p2 was really p1. This was set up in line 3, which is an assignment of a form that is disallowed, and this is exactly why line 3 is disallowed.
Assigning a char ** to a const char ** (as in line 3, and in the original question) is not immediately dangerous. But it sets up a situation in which p2's promise--that the ultimately-pointed-to value won't be modified--cannot be kept.
(C++ has more complicated rules for assigning const-qualified pointers which let you make more kinds of assignments without incurring warnings, but still protect against inadvertent attempts to modify const values. C++ would still not allow assigning a char ** to a const char **, but it would let you get away with assigning a char ** to a const char * const *.)
In C, if you must assign or pass pointers which have qualifier mismatches at other than the first level of indirection, you must use explicit casts (e.g. (const char **) in this case), although as always, the need for such a cast may indicate a deeper problem which the cast doesn't really fix.
References: ISO Sec. 6.1.2.6, Sec. 6.3.16.1, Sec. 6.5.3
H&S Sec. 7.9.1 pp. 221-2
See if this would work for you:
f(struct_type *data);
void test(unsigned n)
{
struct_type *v = malloc(n * sizeof(struct_type *));
f(v);
}
Please let me know how you get on.
Edited based on Rampion's answer. The problem is with the double const in f()'s declaration.
Code with the warning:
struct_type ** v;
v = (struct_type **)malloc(10 * sizeof(struct_type *));
f(v);
This compiles without warning:
const struct_type *const* v;
v = (const struct_type **)malloc(10 * sizeof(struct_type *));
f(v);
f expects to get as input an array of pointers (const struct_type* []). You pass a pointer to a pointer of struct (const struct_type**).
The best thing to do, IMO, is to change the signature of f to:
void f(const struct_type *const* data);
Why do you need to pass arrays as arguments to functions?

Resources