17

I was always under the impression that const char **x was the correct type to use for a dynamically allocated array of const strings, like so:

#include <stdlib.h>

int main()
{
    const char **arr = malloc(10 * sizeof(const char *));
    const char *str = "Hello!";
    arr[0] = str;
    free(arr);
}

However, when compiling this code with VS2017, I get this warning on the free line:

warning C4090: 'function': different 'const' qualifiers

Is there something wrong with my code? FWIW, when I compile with GCC, I don't get any warnings, even with -Wall -Wextra -pedantic.

Lundin
  • 195,001
  • 40
  • 254
  • 396
Andrew Sun
  • 4,101
  • 6
  • 36
  • 53
  • 6
    The program as posted is absolutely fine, the warning is wrong. – n. m. could be an AI Mar 23 '17 at 06:50
  • 3
    Code looks fine. What if to remove the warning you can typecaste `arr` ie. `free((void*)arr);` – Shihab Pullissery Mar 23 '17 at 07:07
  • `error C2440: 'initializing': cannot convert from 'void *' to 'const char **'` VS2015 is giving me the aforementioned error for the line with malloc. An explicit type cast fixes it though. – Yashas Mar 23 '17 at 08:17
  • 1
    Are you sure you compiled the code as C in VS? And not as C++? – Lundin Mar 23 '17 at 09:21
  • @Lundin Yes, it's compiled with the `/TC` flag. Compiling as C++ yields an error on the `malloc`line due to the `void*` to `const char **` conversion. – Andrew Sun Mar 23 '17 at 13:39
  • @AndrewSun Yet another incorrect diagnostic from VS then. The most disturbing thing with this one is that it suggests that there's something fundamentally wrong with the compiler's type system. I would strongly recommend to use another compiler for C code. – Lundin Mar 23 '17 at 14:34

2 Answers2

6

There is nothing wrong with your code. The rules for this are found in the C standard here:

6.3.2.3 Pointers
A pointer to void may be converted to or from a pointer to any object type. A pointer to any object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.

For any qualifier q, a pointer to a non-q-qualified type may be converted to a pointer to the q-qualified version of the type; the values stored in the original and converted pointers shall compare equal.

Meaning that any pointer to object type (to a variable) may be converted to a void pointer, unless the pointer is qualified (const or volatile). So it is fine to do

void* vp; 
char* cp; 
vp = cp;

But it is not ok to do

void* vp; 
const char* cp; 
vp = cp; // not an allowed form of pointer conversion

So far so good. But when we mix in pointer-to-pointers, const-ness is a very confusing subject.

When we have a const char** arr, we have a pointer to pointer to constant char (hint: read the expression from right to left). Or in C standard gibberish: a pointer to a qualified pointer to type. arr itself is not a qualified pointer though! It just points at one.

free() expects a pointer to void. We can pass any kind of pointer to it unless we pass a qualified pointer. const char** is not a qualified pointer, so we can pass it just fine.

A qualified pointer to pointer to type would have been char* const*.

Note how gcc whines when we try this:

char*const* arr = malloc(10 * sizeof(char*const*));
free(arr);

gcc -std=c11 -pedantic-errors -Wall - Wextra:

error: passing argument 1 of 'free' discards 'const' qualifier from pointer target type

Apparently, Visual Studio gives an incorrect diagnostic. Or alternatively you compiled the code as C++, which doesn't allow implicit conversions to/from void*.

Lundin
  • 195,001
  • 40
  • 254
  • 396
1

The assignment is valid for the same reason the following assignment is valid.

const char** arr = malloc(10 * sizeof(const char *));
void* p = arr;

This rule1 explains that the, if both operands are pointer types, the type left pointer is pointing to, must have the same qualifiers, as the type the right pointer is pointing to.

The right operand is a pointer that is pointing to a type that doesn't have any qualifiers. This type being type a type pointer to a const char (const char*). Don't let that const qualifier confuse you, that qualifier dont't belong to the pointer type.

The left operand is a pointer that is pointing to a type that also doesn't have any qualifiers. The type being void. So the assignment is valid.

If the pointer is pointing to a type that has qualifiers, then the assignment would not be valid:

const char* const* arr = malloc(10 * sizeof(const char *));
void* p = arr;    //constraint violation

The right operand is a pointer that is pointing to a type with the qualifier const, this type being a type const pointer to a const char (const char* const).

The left operand is a pointer that is pointing to a type without any qualifiers, this type being type void. The assignment violates the constraint1.


1 (Quoted from: ISO/IEC 9899:201x 6.5.16.1 Simple assignment Constraints 1)
the left operand has atomic, qualified, or unqualified pointer type, and (considering the type the left operand would have after lvalue conversion) one operand is a pointer to an object type, and the other is a pointer to a qualified or unqualified version of void, and the type pointed to by the left has all the qualifiers of the type pointed to by the right;

2501
  • 25,460
  • 4
  • 47
  • 87
  • Language-lawyer note: if the pointer conversion is done in some other way than simple assignment, for example by passing the pointer as parameter to a function (as in the OP), it is not a constraint violation, but undefined behavior. Since 6.3.2.3 does not mention at all what will happen when you convert a qualified pointer to a non-qualified pointer. – Lundin Mar 23 '17 at 09:27
  • @Lundin - In regard to passing the pointer as an argument to a function. [6.5.2.2/4](http://port70.net/~nsz/c/c11/n1570.html#6.5.2.2p4) says *"In preparing for the call to a function, the arguments are evaluated, and each parameter is assigned the value of the corresponding argument"*. Now, it's possible that the standard uses the term "assignment" with two different meanings, but if it isn't so then passing as an argument to a function is a constraint violation as well, is it not? – StoryTeller - Unslander Monica Mar 23 '17 at 09:53
  • @StoryTeller Actually it would seem that 6.5.2.2/2 is the relevant part. _"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."_ So it turns out it is a constraint violation after all, but a constraint violation of 6.5.2.2/2. – Lundin Mar 23 '17 at 10:33