11

In C, const char *p is sometimes called a "read-only" pointer: a pointer to a constant object (in this case, a char).

It would seem that either

  1. const char **p
  2. const char *const *p

would be the equivalent declarations for a read-only pointer to a pointer, depending on how many levels of indirection are immutable.

However, compilers (gcc, clang) generate a warning.

My Questions: How do you pass a pointer to a pointer (like char **p) to a function as a "read-only" pointer without generating a warning? If an explicit cast is required, why in the case of char **p and not char *p?

More Details

Here is a concrete example of what I'm trying to achieve.

Read-only Pointer

This code treats char *ptr as a read-only pointer.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void readonly(const char *ptr)
{
    // ... do something with ptr ...

    // but modifying the object it points to is forbidden
    // *ptr = 'j';  // error: read-only variable is not assignable
}

int main(void)
{
    char *ptr =  malloc(12*sizeof(char));
    strcpy(ptr, "hello world");

    printf("before: %s\n", ptr);
    readonly(ptr);
    printf("after: %s\n", ptr);

    free(ptr);
    return 0;
}

The qualifier const is added in the function call without any complaints.

Read-only Pointer to Pointer

I would expect that a similar function call should be possible with a pointer to a pointer.

void readonly(const char *const *ptr)
{
    //  ... do something with ptr ...

    // but modifying the object it points to is forbidden
    // **ptr = 'j';
}

int main(void)
{
    char **ptr;

    ptr = (char **) malloc(2*sizeof(char *));

    ptr[0] = malloc(14*sizeof(char));
    strcpy(ptr[0], "hello world 0");

    ptr[1] = malloc(14*sizeof(char));
    strcpy(ptr[1], "hello world 1");

    printf("before: %s %s\n", ptr[0], ptr[1]);
    readonly(ptr);
    printf("after: %s %s\n", ptr[0], ptr[1]);

    free(ptr[1]);
    free(ptr[0]);
    free(ptr);
    return 0;
}

The clang compiler (version 6.0.0) gives the most human-readable warning.

warning: passing 'char **' to parameter of type
    'const char *const *' discards qualifiers in nested pointer types
    [-Wincompatible-pointer-types-discards-qualifiers]
    readonly(ptr);
         ^~~
note: passing argument to parameter 'ptr' here
    void readonly(const char *const *ptr)

But gcc (8.1.1) also gives a warning.

Aside: It seems strange that clang says that passing char ** discards the qualifier, when I'm trying to add the qualifier?

The Questions

  1. How do you pass a pointer to a pointer (like char **p) to a function as a "read-only" pointer without generating a warning?

  2. If an explicit cast is required, why in the case of char **p and not char *p?

Kris
  • 111
  • 5
  • 1
    The equivalent read-only pointer to pointer is `char *const *ptr`. – melpomene Jul 14 '18 at 18:49
  • 1
    "… is sometimes called a "read-only" pointer:…" - not by people knowing the language. The pointer is read/writable. You're completely misslead. Have a look at cdecl and check your textbook. – too honest for this site Jul 14 '18 at 19:08
  • @melpomene it is not. const pointer to pointer is `const char ** const pointer`. The pointer stars count in the opposite direction - see my answer and example – 0___________ Jul 14 '18 at 19:26
  • @PeterJ_01 I'm not talking about a const pointer to pointer. See the first sentence of the question: We're talking about "*a pointer to a constant object*", i.e. a pointer to a const pointer to char. The "constant object" is the pointer to char. – melpomene Jul 14 '18 at 19:33
  • @melpomene but you declare pointer to const pointer instead, not the const pointer to pointer – 0___________ Jul 14 '18 at 19:33
  • @PeterJ_01 What do you mean, "instead"? – melpomene Jul 14 '18 at 19:35
  • `char *const *ptr` is pointer a to "read-only" pointer. It is not the "read only" pointer to pointer – 0___________ Jul 14 '18 at 19:38
  • @PeterJ_01 You did not read the first sentence of the question: "*a "read-only" pointer: a pointer to a constant object*", i.e. OP is using "read-only pointer" to refer to a pointer you can only read through. Which is `const T *`. – melpomene Jul 14 '18 at 19:41
  • in your comment this declaration is explained as "read only" pointer to pointer which is wrong – 0___________ Jul 14 '18 at 19:43
  • No, it is correct. It is a pointer to a constant object, which by the definition given by OP makes it a "read-only pointer". – melpomene Jul 14 '18 at 19:44
  • @melpomene it is wrong. Lets ask an impartial authority cdecl.org: `char * const * p;` is .... `declare p as pointer to const pointer to char` – 0___________ Jul 14 '18 at 19:50
  • @Kris you need ** pointers if you need to change the pointer itself, not only the object - for example you reallocate the memory. – 0___________ Jul 14 '18 at 19:54
  • @PeterJ_01 cdecl.org does not use OP's terminology. In fact, it does not mention "read-only" at all. I don't see what your point is. – melpomene Jul 14 '18 at 19:57
  • I'm not discussing anything. That's as it is. Using the correct and standard terminology and consistent declarations avoids missunderstanding. Plus from this you should be able to find out yourself, no need to ask for each declaration a little more complex than `int i`. – too honest for this site Jul 14 '18 at 20:01
  • Regarding the terminology mentioned by @Olaf: "C in a Nutshell, Second Edition" (Chapter 9, section Pointers and Type Qualifiers) is one book that uses this language. What I'm looking for is a pointer to a pointer to a constant object OR a pointer to a constant pointer to a constant object. Either way, you can "read" the object with **p, but not modify it. – Kris Jul 14 '18 at 20:02
  • @melpomene: That's right. cdecl uses the correct and standard terminology. OP uses wrong and missleading constructs of standard terms. This causes missunderstanding and missconception at OP. See ^, too – too honest for this site Jul 14 '18 at 20:02
  • That's not what **you** wrote, so it does not support your (or @melpomene's) position. In programming **clear and precise wording matters**. – too honest for this site Jul 14 '18 at 20:04
  • @Olaf in the third comment I have explained why melpomene is wrong. `char *const *ptr` is not the `const pointer to pointer` it is the `pointer to const pointer` – 0___________ Jul 14 '18 at 20:07
  • @PeterJ_01: In case I wasn't clear enough: You are right. I well know how to read (and write) a declaration. – too honest for this site Jul 14 '18 at 20:28
  • @Olaf where did I write that you do not know? And actually I was correcting the melpomenes ("read only" I understood as synonymous of the `const`) comment. I think the same mistake makes the OP as order of the declaration of multisatrs pointers may be a bit confusing. But I still do not understand DVs – 0___________ Jul 14 '18 at 20:39
  • @PeterJ_01: Not my DVs, if you implied that. I just don't understand the upvotes for the question. This subnject has been chewed over and over here and there and everywhere. – too honest for this site Jul 14 '18 at 20:50
  • I try you code on https://godbolt.org/ with different version of compiler and seems to work well, no warning or errors are raised. – Zig Razor Oct 08 '20 at 09:01

4 Answers4

1

Your function readonly(const char *ptr) promises that it will not touch what is behind the pointer.
That's the reason why a caller may trustfully pass a pointer to a const memory area (passing a const char *ptr).
And of course it is no problem to pass a non-const char *ptr to this function as well, because your function promises more security than needed. That's why the compiler follows your wish without any warning or note whatsoever.

However this automatism does only work for the 1st indirection level.

The Standard claims in 6.5.16.1 for an assignment, 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"
The last part of the sentence means that adding a qualifier to the pointed-to type is no problem.
And the first part claims "compatible" types. And (I think,) 6.7.3 (11) does describe this for qualified types: "For two qualified types to be compatible, both shall have the identically qualified version of a compatible type."

Reading this, your pointed-to types are not considered as compatible (even if it would be possible to assign one to the other).

Hence I would say that the clang warning about discarding qualifiers is a bit misleading, but it refers to the non-identically qualified pointed-to types.

MattTT
  • 339
  • 3
  • 9
0

You can simplify your code to this:

char const ** var = (char**)0;

The second fact. It is not a bug. Here are test cases: https://github.com/llvm/llvm-project/blob/62ec4ac90738a5f2d209ed28c822223e58aaaeb7/clang/test/Sema/pointer-conversion.c

Let's go with another example:

char const*****  c1 = (char*****)0; // fail
char *const****  c2 = (char*****)0; // fail
char **const***  c3 = (char*****)0; // fail
char ***const**  c4 = (char*****)0; // fail
char ****const*  c5 = (char*****)0; // ok
char *****const  c6 = (char*****)0; // ok

Good. We see pattern.

As I guess C/C++ can't promise you that you can modify anything with a depth bigger than 2 due some problems with const-correctness, which is discussed here before.

0

In GCC, you can just disable the warning in code with:

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wincompatible-pointer-types-discards-qualifiers"
readonly(ptr);
#pragma GCC diagnostic pop

I imagine clang has something similar. As for your second question, the comments seem to have it.

-1

A I understand you want to declare the const pointer to pointer to const chat similar as pointer to const char. the declaration is :

void foo( const char ** const pointer )

It is a bit "tricky" as the stars count from the opposite direction.

Lets consider the very simple example

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void foo( const char ** const pointer )
{
    while(**pointer)
    {
        printf("%c", **pointer);
        (*pointer)++;
    }
    printf("\n");
}

void foo1( const char *const * pointer)
{
    while(*pointer)
    {
        printf("%s\n", *pointer++);
    }
}


int main(void) 
{
    char **pointer = malloc(sizeof(char *));
    char **pointer1 = malloc(sizeof(char *) * 4);

    char strings_in_RAM_0[] = "one";
    char strings_in_RAM_1[] = "two";
    char strings_in_RAM_2[] = "three";

    pointer1[0] = strings_in_RAM_0;
    pointer1[1] = strings_in_RAM_1;
    pointer1[2] = strings_in_RAM_2;
    pointer1[3] = NULL;

    *pointer = malloc(50);

    strcpy(*pointer,"Const pointer to pointer");

    foo(pointer);
    foo1(pointer1);

    free(*pointer);
    free(pointer);
    free(pointer1);

    return 0;
}

it will give you a warning but it will work as expected until the free. Then (because the pointer is changed) the free fails. To avoid it you need to save the original pointer to char for example:

void foo( const char ** const pointer )
{
    const char *ptr = *pointer;
    while(**pointer)
    {
        printf("%c", **pointer);
        (*pointer)++;
    }
    printf("\n");
    *pointer = ptr;
}

the foo1 function parameter is of type pointer to const pointer to const char

https://ideone.com/JSewN4

0___________
  • 60,014
  • 4
  • 34
  • 74
  • 1
    `foo(pointer);` is a type error. Converting from `char **` to `const char **` is not safe. – melpomene Jul 14 '18 at 19:35
  • Interesting - why it is not safe in your poinion? It is same safe as any other multistar pointer to const objects – 0___________ Jul 14 '18 at 19:37
  • 1
    `char *p; char **pp; const char **ppc; pp = &p; ppc = pp; *ppc = "foo"; *p = 'x';` – melpomene Jul 14 '18 at 19:40
  • @melpomene same dangerous as 'char *p1; const char *p2; p1 = p2; *p1 = 'a';' the only difference is another warning. – 0___________ Jul 14 '18 at 19:46
  • 2
    It's not just "another warning"; it's a type error. C makes it an error because it removes `const`, which is a bad thing. Please don't recommend that people ignore type errors in their programs. – melpomene Jul 14 '18 at 19:50
  • @melpomene You should not comment something I did not write. I did not recommend anything, and your examples do not show anything. See also my another comment about your wrong type explanation – 0___________ Jul 14 '18 at 19:53
  • 1
    My example shows that converting from `char **` to `const char **` is unsafe because it lets you convert `const char *` to `char *`, which silently strips `const` away, which is unsafe. – melpomene Jul 14 '18 at 19:55
  • 1
    You answered a question on SO. You provided example code, saying "*it will give you a warning but it will work as expected*". If you do not recommend people do this, why post it at all? – melpomene Jul 14 '18 at 19:56
  • @melpomene I think you do not understand the declarations of `**` pointers and my examples to show what is const and what is not. – 0___________ Jul 14 '18 at 19:56