1

This is a silly question, but I can't seem to get it right. I came across another question, but the answer given doesn't properly address the warnings in my specific use case.

I'm trying to declare an array of constant strings to pass as argv in posix_spawn function, but GCC complains about const being discarded. See a sample code below:

#include <stdio.h>

/* Similar signature as posix_spawn() shown for brevity. */
static void show(char *const argv[])
{
    unsigned i = 0;

    while(argv[i] != NULL) {
        printf("%s\n", argv[i++]);
    }
}

int main(void)
{
    const char exe[] = "/usr/bin/some/exe";

    char *const argv[] = {
        exe,
        "-a",
        "-b",
        NULL
    };

    show(argv);

    return 0;
}

And compile it as:

gcc -std=c89 -Wall -Wextra -Wpedantic -Wwrite-strings test.c -o test 
test.c: In function ‘main’:
test.c:17:9: warning: initializer element is not computable at load time [-Wpedantic]
         exe,
         ^
test.c:17:9: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
test.c:18:9: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
         "-a",
         ^
test.c:19:9: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
         "-b",
         ^

Since, exe is itself a constant string like "-a" and "-b", I thought it's correct. But GCC seems to disagree. Removing exe from the array and removing -Wwrite-strings compiles without warnings. Maybe I am missing something too basic.

How does one declare a const array of strings?

Unmanned Player
  • 1,109
  • 9
  • 30
  • "I came across another question" - the accepted answer to that question is correct (and there are no "warnings"), however that question does not involve `const` in any way so it is not clear why you have linked it – M.M May 30 '18 at 03:39
  • The op was trying to put literal constant strings inside a non-const array. GCC normally warns. And in my case due to `-Werror`, it fails with errors instead. – Unmanned Player May 30 '18 at 04:10
  • String literals are not `const` in C. The code was correct. You see warnings because you use the switch `-Wwrite-strings` which intentionally produces warnings for correct code. – M.M May 30 '18 at 04:11
  • I thought string literals are stored in read-only section irrespective of warning switches, unless `-fwritable-strings` is also used. But you're right, the standard doesn't specify such a behaviour though. Corrected my question's wording. Thanks. – Unmanned Player May 30 '18 at 04:18
  • They could be stored in read-only area, but either way there is no `const` qualifier. It is just undefined behaviour with no diagnostic to write them, in Standard C. The `-Wwrite-strings` attempts to catch write attempts, but also gives false positives when you need to call an API that isn't const-correct – M.M May 30 '18 at 04:25

2 Answers2

1

The declaration char *const argv[] makes argv an array of constant pointers to mutable chars. Once you create the array, you cannot change where the pointers point (you can't say argv[0] = "/bin/some_other_program"), but you can change the characters themselves (so argv[1][1] = 'b', to make it so you pass -b instead).

However, the pointers you are assigning as elements of argv are pointers to constant chars. It's undefined behavior to actually do any of those assignments even if the type of argv will let you, hence the warning. Since you can't change the type of the function, you need to get rid of the constness somehow. Options are to use strdup, to put the strings into char[] variables (which implicitly also copies them), or to cast away the const at this level (which is UB if the program actually modifies the values but should be safe if it does not). Here are examples using the latter two methods.

Daniel H
  • 7,223
  • 2
  • 26
  • 41
  • Changing it like your suggestion doesn't match with posix_spawn() function signature. See http://pubs.opengroup.org/onlinepubs/009696899/functions/posix_spawn.html. Does this mean, I have to `strdup()` every string inside `argv`? – Unmanned Player May 29 '18 at 22:30
  • No, if you create arrays of pointers to non-`const` `char`s (for example, remove the `const` from `exe`), it should work. But I am somewhat surprised; I would not expect `posix_spawn` to want to actually change the values passed in so I don't know why it wants them mutable. I'm not certain, but I also think as long as it doesn't *actually* change anything it is perfectly defined to cast away the `const`ness of the pointers. – Daniel H May 29 '18 at 22:49
  • 2
    once the pointers get deep, const becomes very tricky and hard to understand: https://stackoverflow.com/questions/5055655/double-pointer-const-correctness-warnings-in-c – Evan Benn May 29 '18 at 23:37
  • @EvanBenn Yeah, definitely. I suppose that could change details of why the API is designed with non-`const` `char`s, but I don't think it changes my answer. – Daniel H May 30 '18 at 00:03
  • @EvanBenn Makes sense. But how do I avoid this specific case here? Use `pragama`? – Unmanned Player May 30 '18 at 01:47
  • @UnmannedPlayer Daniel H's answer is good, you could be strdup'ing or similar, IF you want to heed the -Wwrite-strings warning. Presumably posix_spawn will not modify the strings, so it is reasonably safe to 'discard the const qualifier'. The common librarys are full of such things eg char* index(const char* input...). Const correctness is a battle that cannot be won in C. – Evan Benn May 30 '18 at 07:32
  • It is not required to copy the actual content of the strings ; you can safely cast `const char *` to `char *` and then read the const strings through that pointer. – M.M May 31 '18 at 02:32
  • @M.M Yep, and I do say that. On some level I don't like that solution because my instincts were formed in C++ where the safe conversions are actually possible and people design APIs accordingly (or those who don't shouldn't be trusted to not actually modify the strings), but it's probably what I'd go with too. – Daniel H May 31 '18 at 02:52
0

The function in question, posix_spawn, expects a pointer to (the first element of) an array of pointer to non-const char. Despite this, the function does not modify the character strings.

For historical reasons this situation of poor const-correctness in C APIs is not uncommon.

You can pass string literals to it without any warning in Standard C, because string literals have type: array of non-const char. (Modifying them is undefined behaviour with no diagnostic required).

If you use the -Wwrite-strings flag to try and get warnings about potential UB then it will give you these false positives for the cases where you interact with a non-const-correct API.


The intended way to consume the API in C89 would be to use:

char exe[] = "/usr/bin/some/exe";

char * argv[] = {
    NULL,
    "-a",
    "-b",
    NULL
};

argv[0] = exe;
show(argv);

Note that in C89 it is not possible to have your array be const and also be initialized with the local variable exe. This is because C89 requires all initializers in a braced list to be constant expressions, the definition of which excludes the address of a local variable.

If you want to use -Wwrite-strings then you could suppress the warning by using (char *)"-a" instead of "-a" and so on.


It was suggested in comments to use const char * for the string type and then use a cast, like so:

const char exe[] = "/usr/bin/some/exe";

const char * argv[] = {
    NULL,
    "-a",
    "-b",
    NULL
};
argv[0] = exe;
show((char **)argv);

However this probably violates the strict aliasing rule. Even though the rule allows a const T to be aliased as a T, this rule does not "recurse" ; it is probably not allowed to alias const char * as char * , although the rule's wording is not 100% clear. See this question for further discussion.

If you wanted to offer a wrapper that accepts const char ** and calls posix_spawn , and not violate the C Standard, then you would actually have to make a copy of the pointer list into an array of char *. (But you do not have to actually copy the string content)

M.M
  • 138,810
  • 21
  • 208
  • 365
  • I can't modify the signature of `posix_spawn` so it accepts `const char *const []`, no? Also, your answer works for this instance though. I have changed my code to wrap `posix_spawn` so it works like how you've shown in the answer. – Unmanned Player May 30 '18 at 04:13