6

This is the prototype for execv:

int execv(const char *path, char *const argv[]);

Can I pass an array of const char pointers as the second argument?

This example program gives a warning when USE_CAST is not set:

#include <unistd.h>
int main(int argc, char *argv[])
{
    if (argc > 0) {
        const char *exe_name = "/bin/echo", *message = "You ran";
        const char *exe_args[] = { exe_name, message, argv[0], NULL };
#ifdef USE_CAST
    execv("/bin/echo", (char **) exe_args);
#else
    execv("/bin/echo", exe_args);
#endif
    }
    return 0;
}

When compiling, gcc says, "passing argument 2 of 'execv' from incompatible pointer type" if I don't use the cast.

From the POSIX documentation for execv (halfway through the Rationale section), it looks like the second argument is a char *const array only for backwards compatibility:

The statement about argv[] and envp[] being constants is included to make explicit to future writers of language bindings that these objects are completely constant. ... It is unfortunate that the fourth column cannot be used...

where the "fourth column" refers to const char* const[].

Is the (char **) cast safe to use here? Should I create a char * array and pass that to execv instead?

yellowantphil
  • 1,483
  • 5
  • 21
  • 30

2 Answers2

4

Can I pass an array of const char pointers as the second argument?

Well yes, you already know that you can cast in order to do so.

From the POSIX documentation for execv (halfway through the Rationale section), it looks like the second argument is a char *const array only for backwards compatibility:

I wouldn't put it in those terms, but yes, there is a compatibility aspect to the chosen signature. The section you reference explains that C does not have a wholly satisfactory way to express the degree of const-ness that POSIX requires execv() to provide for the arguments. POSIX guarantees that the function will not change either the pointers in argv or the strings to which they point.

With that being the case, I think it not unreasonable to cast the argv pointer as you propose to do, though I would leave a comment in my code explaining why doing so is safe.

On the other hand, you should consider simply leaving the const off of your array declaration:

char *exe_name = "echo", *message = "You ran";
char *exe_args[] = { exe_name, message, argv[0], NULL };

Or, in your simple example, even this would do:

char *exe_args[] = { "echo", message, argv[0], "You ran", NULL };

C string literals correspond to arrays of type char, not const char, so this is perfectly legal as far as C is concerned, even though actually trying to modify the contents of those strings might fail.

On the third hand, modern C has array literals, so you could even do this:

execv("/bin/echo", (char *[]) { "echo", "You ran ", argv[0], NULL });

In that last case you don't even have a cast (the thing that resembles one is just part of the syntax for an array literal).

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • Somehow I thought that string literals were `const char`. I guess taking `const` off of the variables I'm using isn't so bad after all, although I'm starting to think that either I misunderstand const-correctness, or it isn't always practical in C. – yellowantphil Apr 28 '16 at 22:18
  • @anaranjada, nope, the standard specifies that they are arrays of type `char`, even though in practice they may be unwritable. GCC has an option to use arrays of `const char` instead, to help you find places where an attempt might be made to write to one -- perhaps that's what you were thinking of. (And `gcc` is for that reason non-conforming when you enable that option.) – John Bollinger Apr 29 '16 at 00:36
  • Yes, I think the GCC option threw me off. Anyway, I removed a handful of `const` qualifiers from my code, and it compiles without warnings now. Thanks. – yellowantphil Apr 29 '16 at 06:30
1

If you're just going to cast away the const, then you shouldn't use const to begin with. Most (dare I say all) compilers will accept the following code

char *exe_name = "/bin/echo";
char *message = "You ran";
char *exe_args[] = { exe_name, message, argv[0], NULL };
execv( exe_args[0], exe_args );

If that is not pedantically correct enough for you, then the other option is

char exe_name[] = "/bin/echo";
char message[] = "You ran";
char *exe_args[] = { exe_name, message, argv[0], NULL };
execv( exe_args[0], exe_args );

Note that execv is going to make copies of the strings (to create the argv array for the executable), so it doesn't matter whether the strings are actually const or not.

user3386109
  • 34,287
  • 7
  • 49
  • 68
  • Yes, you do dare, at least if you're willing to restrict your statement to *conforming* compilers. Per the standard, a string literal corresponds to a static array with elements of type (non-`const`) `char`. As such, you don't even need to use intermediate variables to form the array initializer. – John Bollinger Apr 28 '16 at 21:57