Best practice to use execvp in C++
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
You hit a real problem because we are facing two incompatible constraints:
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.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
.