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?

5
  • "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 Commented May 30, 2018 at 3: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. Commented May 30, 2018 at 4: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. Commented May 30, 2018 at 4: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. Commented May 30, 2018 at 4: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 Commented May 30, 2018 at 4:25

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

8 Comments

Changing it like your suggestion doesn't match with posix_spawn() function signature. See pubs.opengroup.org/onlinepubs/009696899/functions/…. Does this mean, I have to strdup() every string inside argv?
No, if you create arrays of pointers to non-const chars (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 constness of the pointers.
once the pointers get deep, const becomes very tricky and hard to understand: stackoverflow.com/questions/5055655/…
@EvanBenn Yeah, definitely. I suppose that could change details of why the API is designed with non-const chars, but I don't think it changes my answer.
@EvanBenn Makes sense. But how do I avoid this specific case here? Use pragama?
|
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)

1 Comment

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.