11

At the beginning, I wrote something like this

char* argv[] = { "ls", "-al", ..., (char*)NULL };
execvp("ls", argv);

However, GCC popped up this warning, "C++ forbids converting a string constant to char*."

Then, I changed my code into

const char* argv[] = { "ls", "-al", ..., (char*)NULL };
execvp("ls", argv);

As a result, GCC popped up this error, "invalid conversion from const char** to char* const*."

Then, I changed my code into

const char* argv[] = { "ls", "-al", ..., (char*)NULL };
execvp("ls", (char* const*)argv);

It finally works and is compiled without any warning and error, but I think this is a bit cumbersome, and I cannot find anyone wrote something like this on the Internet.

Is there any better way to use execvp in C++?

Kevin Dong
  • 5,001
  • 9
  • 29
  • 62
  • 1
    Well to begin with, the terminating `(char*)0` should be either a plain `0`, or preferably `nullptr`. As for your problem with the array, I recommend you [read the `execvp` manual page](http://man7.org/linux/man-pages/man3/execvp.3.html). There you will see the type of arguments, and can then define the array to be of that type (with an added `const` at the beginning). – Some programmer dude Nov 02 '17 at 06:33
  • Most of the manual pages are for C, but not for C++. I would like to know how to use `execvp` in C++ code. – Kevin Dong Nov 02 '17 at 06:36
  • 2
    It's still the *same function* you're calling. The POSIX system interface functions are all C functions you call. – Some programmer dude Nov 02 '17 at 06:38
  • Actually the first code snippet can be compiled perfectly under gcc, but it will pop up warnings under g++, so this is why I would like to know how to write in a C++ way. ;-) – Kevin Dong Nov 02 '17 at 06:39
  • 3
    ***One*** difference between C and C++ is that in C string literals aren't really constant, while in C++ they are. – Some programmer dude Nov 02 '17 at 06:42
  • @Someprogrammerdude: The last argument of `execvp` should be neither `0` nor `nullptr`. Both are **Undefined Behavior**. Since `execvp(...)` is a vararg function, it reads all its variable arguments as `char*`. It is UB when one of those is not actually a `char*`. – MSalters Nov 02 '17 at 10:58
  • 3
    @MSalters You must think of the `execlp` function? The `execvp` function is *not* a vararg function. And even when using `execlp` the last "argument" should not be explicitly casted, and preferably be using the `nullptr` keyword than `0`. – Some programmer dude Nov 02 '17 at 11:26
  • 2
    @Someprogrammerdude: Whoops, yes. Was looking at my own code which indeed uses `execl`. And no, passing a `nullptr_t` to a vararg function that uses `va_arg(arg, char*)` is UB. Type mismatch. The point of `nullptr` in C++ is that it _converts_ to a null pointer of the destination type, but a C vararg function call expression has no destination type. – MSalters Nov 02 '17 at 12:04

3 Answers3

8

You hit a real problem because we are facing two incompatible constraints:

  1. One from the C++ standard requiring you that you must use const char*:

    In C, string literals are of type char[], and can be assigned directly to a (non-const) char*. C++03 allowed it as well (but deprecated it, as literals are const in C++). C++11 no longer allows such assignments without a cast.

  2. The other from the legacy C function prototype that requires an array of (non-const) char*:

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

By consequence there must be a const_cast<> somewhere and the only solution I found is to wrap the execvp function.

Here is a complete running C++ demonstration of this solution. The inconvenience is that you have some glue code to write once, but the advantage is that you get a safer and cleaner C++11 code (the final nullptr is checked).

#include <cassert>
#include <unistd.h>

template <std::size_t N>
int execvp(const char* file, const char* const (&argv)[N])
{
  assert((N > 0) && (argv[N - 1] == nullptr));

  return execvp(file, const_cast<char* const*>(argv));
}

int main()
{
  const char* const argv[] = {"-al", nullptr};
  execvp("ls", argv);
}

You can compile this demo with:

g++ -std=c++11 demo.cpp 

You can see a similar approach in the CPP Reference example for std::experimental::to_array.

Picaud Vincent
  • 10,518
  • 5
  • 31
  • 70
4

This is a conflict between the declaration of execvp() (which can't promise not to modify its arguments, for backwards compatibility) and the C++ interpretation of string literals as arrays of constant char.

If the cast concerns you, your remaining option is to copy the argument list, like this:

#include <unistd.h>
#include <cstring>
#include <memory>
int execvp(const char *file, const char *const argv[])
{
    std::size_t argc = 0;
    std::size_t len = 0;

    /* measure the inputs */
    for (auto *p = argv;  *p;  ++p) {
        ++argc;
        len += std::strlen(*p) + 1;
    }
    /* allocate copies */
    auto const arg_string = std::make_unique<char[]>(len);
    auto const args = std::make_unique<char*[]>(argc+1);
    /* copy the inputs */
    len = 0;                    // re-use for position in arg_string
    for (auto i = 0u;  i < argc;  ++i) {
        len += std::strlen(args[i] = std::strcpy(&arg_string[len], argv[i]))
            + 1; /* advance to one AFTER the nul */
    }
    args[argc] = nullptr;
    return execvp(file, args.get());
}

(You may consider std::unique_ptr to be overkill, but this function does correctly clean up if execvp() fails, and the function returns).

Demo:

int main()
{
    const char *argv[] = { "printf", "%s\n", "one", "two", "three", nullptr };
    return execvp("printf", argv);
}
one
two
three
Toby Speight
  • 27,591
  • 48
  • 66
  • 103
  • Alternatively, consider `std::vector` instances for `arg_string` and `args`, and passing `args.data()` to `execvp()`. – Toby Speight Mar 23 '23 at 13:29
0

execvpe requires an char *const argv[] as it's second argument. That is, it requires a list of const pointers to non-const data. String literals in C are const hence the problem with warnings and casting your argv to char* const* is a hack as execvp is now allowed to write to the strings in your argv. Te solution I see is to either allocate a writeble buffer for each item or just use execlp instead which works with const char* args and allows for passing string literals.

Botond Dénes
  • 595
  • 4
  • 9