3

I have the following variable.

char **arr

Then I want to perform some modification on array which means it can't be declared as a constant.

Now I have a function that accepts the argument of type const char ** arr. But I have no control over the signature of this function.

Now when I cast arr to const char ** arr g++ generates a warning which is [-Werror=cast-qual].

For more clarification consider following MCVE:

#include<cstdio>

void print(const char** x){
    printf("%s", x[0]);
}

int main(int argc, char **argv){
    if(argc>1){
        print((const char **)argv);     
    }
     return 0;
}

//Then compile it as follow:

$ g++ -Wcast-qual test.cpp

//gives the following output:

MCVE.cpp: In function ‘int main(int, char**)’:
MCVE.cpp:5:36: warning: cast from type ‘char**’ to type ‘const char**’ casts away qualifiers [-Wcast-qual]
   const char ** q = (const char**) argv;


So my question is why this generates a warning? Is there any risk in doing this?

And how to achieve a behavior I want to achieve?

Vimal Patel
  • 211
  • 2
  • 9
  • 1
    There is nothing related to an array in your question. You have pointers. Please provide [A Minimal, Complete, and Verifiable Example (MCVE)](http://stackoverflow.com/help/mcve). Yes there is a risk. Never accept code until it compiles without warning. Guessing, you need `char * const *`. – David C. Rankin Jun 30 '20 at 02:26
  • @DavidC.Rankin, I have tried to provide an MCVE. – Vimal Patel Jun 30 '20 at 02:50
  • `const char * const *q = argv;` to make both pointers and each character `const`. – David C. Rankin Jun 30 '20 at 03:00
  • If I say that `q` here represent argument of a function whose signature I can't change then? --- Let me update my example. – Vimal Patel Jun 30 '20 at 03:01
  • 1
    If you can't change the argument to the function from `char**`, then you are pretty much stuck with `char**`. The strings in `argv` are not constant, so you can validly pass it as `char**` -- you will just have to ensure that your function doesn't modify anything if that is your goal, OR make a copy of the the strings. – David C. Rankin Jun 30 '20 at 03:04
  • Can you give me a reason or point out a resource why we can not cast `char **` to `const char **`. – Vimal Patel Jun 30 '20 at 03:08
  • I have done exactly the same. --- `print((const char**)argv)` – Vimal Patel Jun 30 '20 at 03:10
  • Wait a minute. You have to look at the documentation. `-Wcast-qual` will warn `"when making a cast that introduces a type qualifier in an unsafe way."` By casting to `const char**` alone, you introduce the possibility that while the pointers point to an immutable string, the individual characters can still be changed by dereferencing. This is what `-Wcast-qual` protects against. `print` makes no changes -- so the warning is that case (1) is **Understood** and (2) can be **discarded** given the definition of `print()` -- but only in that case. `man g++` gives a good example. – David C. Rankin Jun 30 '20 at 03:15
  • 3
    @VimalPatel Try `const_cast(argv)`. Not sure I understand the g++ warning, but when in C++ cast like C++, I guess. – dxiv Jun 30 '20 at 03:17
  • Seems related: https://stackoverflow.com/a/48608507/206404 – Stephen Newell Jun 30 '20 at 03:18
  • @dxiv -- I was scratching my head a bit too. `man g++` shows `const char **q = (const char **) p;` which permits `*q = "string";` -- which makes `*q` point to a *String Literal*, but also allows `**p = 'b';` -- which would modify the *String Literal* (and likely SegFault) -- which is the backwards purpose for throwing the warning. Which is what `const char * const *q = argv;` protects against, but since the OP can't change the declaration of `print` -- they are just stuck. (don't add `-Wcast-qual` in that case `:)` – David C. Rankin Jun 30 '20 at 03:20
  • 1
    @dxiv, that does not generate any warning. Let me check for other behaviour. – Vimal Patel Jun 30 '20 at 03:23
  • 1
    Chuckling, so the [const_cast conversion](https://en.cppreference.com/w/cpp/language/const_cast) will allow a cast that and will prevent the abuse the `-Wcast-qual` warning protects against with a normal C-style cast. I tried it -- it does -- `**p = '?';` is now an error. @dxiv - seems you should author the answer on this one. – David C. Rankin Jun 30 '20 at 03:29
  • @DavidC.Rankin Indeed. That's an inconsistency at least, and arguably a bug. – dxiv Jun 30 '20 at 03:36
  • 1
    It actually demonstrates the purpose of `const_cast` quite well since it will provide a cast from `char**` to what would be `const char * const *q = argv;`. But I agree that allowing `p` to continue to masquerade as `const char**` is quite inconsistent because in `print (const char **x)`, `x` will no longer behave as `const char **` but as `const char * const *`. I tested that too with `**x = '?';` and the compiler flags it as read only. I wonder how that would work in a library or separate source scenario. Great discussion! – David C. Rankin Jun 30 '20 at 03:45
  • 1
    @DavidC.Rankin I'll leave posting the answer to settle between you and the OP. After all this is one of those rare cases where the question is more clear than the answer ;-) But it was an interesting one, yes. – dxiv Jun 30 '20 at 03:47
  • Related question for `const_cast` but leaves open the inconsistency of how the pointer is subsequently treated when passed as a parameter [Why does const_cast remove constness for a pointer but not for a pointer to a const?](https://stackoverflow.com/q/52111035/3422102) `print` doesn't know whether it has a `"page"` or `"blackboard"`... – David C. Rankin Jun 30 '20 at 03:58
  • @DavidC.Rankin Right. However, oddity remains that `typeid(const_cast(argv))` `==` `typeid((const char **)argv)` yet only the latter triggers the warning. – dxiv Jun 30 '20 at 04:32

1 Answers1

7

Allowing a cast from char** to const char** provides a loophole to modify a const char or a const char*.

Sample code:

const char c = 'A';

void foo(const char** ptr)
{
   *ptr = &c; // Perfectly legal.
}

int main()
{
   char* ptr = nullptr;
   foo(&ptr);  // When the function returns, ptr points to c, which is a const object.
   *ptr = 'B'; // We have now modified c, which was meant to be a const object.
}

Hence, casting a char** to const char** is not a safe cast.


You can use

if(argc>1)
{
   const char* ptr = argv[0];
   print(&ptr);     
}

for your code to compile without the cast-qual warning.

If you need to pass more than just the first argument, you'll need to construct an array of const char* and use it.

if(argc>1)
{
   int N = <COMPUTE N FIRST>;
   const char** ptr = new const char*[N];
   for (int i = 0; i < N; ++i )
   {
      ptr[i] = argv[i];
   }

   print(ptr);

   delete [] ptr; // Make sure to deallocate dynamically allocated memory.
}
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • May be you would like to provide a workaround to achieve the behaviour I want to achieve so that I can accept this as an answer. – Vimal Patel Jun 30 '20 at 04:23
  • @VimalPatel, I am hoping my updated answer provides what you need. – R Sahu Jun 30 '20 at 04:24
  • @RSahu Do you see any reason why `const_cast(argv)` would *not* trigger the same warning, or is that just a `g++` inconsistency? – dxiv Jun 30 '20 at 04:35
  • 1
    @dxiv, I think it should trigger the same warning. I'm going to chalk that up to different execution paths in the compiler and a slip through the cracks. – R Sahu Jun 30 '20 at 04:38