Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best practice to use execvp in C++

Tags:

c++

execvp

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++?

like image 862
Kevin Dong Avatar asked Nov 02 '17 06:11

Kevin Dong


2 Answers

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.

like image 54
Picaud Vincent Avatar answered Sep 28 '22 20:09

Picaud Vincent


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
like image 23
Toby Speight Avatar answered Sep 28 '22 22:09

Toby Speight