10

I have found that the easiest way to build my program argument list is as a vector of strings. However, execv expects an array of chars for the second argument. What's the easiest way to get it to accept of vector of strings?

node ninja
  • 31,796
  • 59
  • 166
  • 254
  • 3
    You can't "get it to accept" anything it wasn't written to accept. You could, however, convert your input into a format it understands. – Mike Daniels Apr 26 '11 at 23:59

6 Answers6

20

execv() accepts only an array of string pointers. There is no way to get it to accept anything else. It is a standard interface, callable from every hosted language, not just C++.

I have tested compiling this:

std::vector<string> vector;
const char *programname = "abc";

const char **argv = new const char* [vector.size()+2];   // extra room for program name and sentinel
argv [0] = programname;         // by convention, argv[0] is program name
for (int j = 0;  j < vector.size()+1;  ++j)     // copy args
        argv [j+1] = vector[j] .c_str();

argv [vector.size()+1] = NULL;  // end of arguments sentinel is NULL

execv (programname, (char **)argv);
smac89
  • 39,374
  • 15
  • 132
  • 179
wallyk
  • 56,922
  • 16
  • 83
  • 148
  • 1
    It looks like you're passing the program name twice to execv, since argv[0] is set as the program name. – node ninja Apr 27 '11 at 00:08
  • When I try to compile the program, it complains about `argv[0] = programname`. It says `test.cpp:10: error: expected constructor, destructor, or type conversion before ‘=’ token` – node ninja Apr 27 '11 at 00:25
  • @z-buffer: Dunno. It compiles fine for me. The only things I haven't shown are includes for `unistd.h`, `string`, and `vector` and `using namespace std;`. – wallyk Apr 27 '11 at 00:28
  • 1
    @wallyk: ...and the fact that the code is inside a function! See http://stackoverflow.com/questions/5798231/why-wont-this-c-program-compile – Greg Hewgill Apr 27 '11 at 01:18
  • @Greg: Quite right. I wrapped the code inside `int main(void) {` ... `return 0;}` so it would compile *and* link. – wallyk Apr 27 '11 at 05:44
  • Is the cast in the call to `execv` legit? The documentation expects an array of constant pointers to modifiable characters, the cast removes that cast and allows `execv` modifying memory that is marked as `const`... not that there would be any reason for `execv` to modify the data but... – David Rodríguez - dribeas Feb 19 '13 at 16:21
  • the use of c_str() concerns me, argv[j+1] could become invalid. – Aaron Sep 29 '14 at 16:32
  • @symphonyblade: Look closely at how argv[] is allocated. – wallyk Sep 29 '14 at 19:22
  • don't we need to free the memory taken by `argv` using `delete`? Where can we do that? – Yoni Zohar Sep 17 '20 at 03:25
  • 1
    @YoniZohar: If `execv` works (without error), the process is overwritten, so no. If `execv` fails, then maybe `delete` would be needed. – wallyk Sep 17 '20 at 10:34
  • I would replace `const char **argv` with `std::vector`, and then call `execv()` with `argv.data()`. – Remy Lebeau Mar 01 '22 at 02:12
3

Yes, it can be done pretty cleanly by taking advantage of the internal array that vectors use. Best to not use C++ strings in the vector, and const_cast string literals and string.c_str()'s to char*.

This will work, since the standard guarantees its elements are stored contiguously (see https://stackoverflow.com/a/2923290/383983)

#include <unistd.h>
#include <vector>

using std::vector;

int main() {
  vector<const char*> command;

  // do a push_back for the command, then each of the arguments
  command.push_back("echo");
  command.push_back("testing");
  command.push_back("1");
  command.push_back("2");
  command.push_back("3");

  // push NULL to the end of the vector (execvp expects NULL as last element)
  command.push_back(NULL);

  // pass the vector's internal array to execvp
  execvp(command[0], const_cast<char* const*>(command.data()));
  return 1;
}

Code adapted from: How to pass a vector to execvp

Do a const_cast to avoid the "deprecated conversion from string constant to 'char*'". String literals are implemented as const char* in C++. const_cast is the safest form of cast here, as it only removes the const and does not do any other funny business. execvp() will not edit the values anyway.

If you want to avoid all casts, you have to complicate this code by copying all the values to char* types not really worth it.

Although if the number of arguments you want to pass to execv/execl is known, it's easier to write this in C.

ericcurtin
  • 1,499
  • 17
  • 20
2

The prototype for execv is:

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

That means the argument list is an array of pointers to null-terminated c strings.

You have vector<string>. Find out the size of that vector and make an array of pointers to char. Then loop through the vector and for each string in the vector set the corresponding element of the array to point to it.

QuantumMechanic
  • 13,795
  • 4
  • 45
  • 66
  • The only problem with this approach is that `std::string` stores it's data typically without the null-terminator. You'd have to use `std::string::c_str()` or include the null-terminator in each string in the vector manuall, prior to calling `execv()`. – Hydranix Oct 18 '16 at 00:40
  • "*`std::string` stores it's data typically without the null-terminator*" - since C++11, `std::string` is required to store a null terminator in its data buffer. But even before C++11, most implementations did that anyway, to keep `c_str()` simple. – Remy Lebeau Sep 14 '21 at 00:47
2

You can't change the how execv works (not easily anyway), but you could overload the function name with one that works the way you want it to:

int execv(const string& path, const vector<string>& argv) {
    vector<const char*> av;
    for (const string& a : argv) {
        av.push_back(a.c_str());
    av.push_back(0);
    return execv(path.c_str(), &av[0]);
}

Of course, this may cause some confusion. You would be better off giving it a name other than execv().

NB: I just typed this in off the top of my head. It may not work. It may not even compile ;-)

Ferruccio
  • 98,941
  • 38
  • 226
  • 299
  • 3
    it does not compile. something is wrong about converting `&argv[0]` from const char** to char* const* – PypeBros Nov 08 '18 at 14:50
2

I stumbled over the same problem a while ago.

I ended up building the argument list in a std::basic_string<char const*>. Then I called the c_str() method and did a const_cast<char* const*> on the result to obtain the list in a format that execv accepts.

For composed arguments, I newed strings (ordinary strings made of ordinary chars ;) ), took their c_str() and let them leak.

The const_cast is necessary to remove an additional const as the c_str() method of the given string type returns a char const* const* iirc. Typing this, I think I could have used std::basic_string<char*> but I guess I had a reason...


I am well aware that the const-casting and memory leaking looks a bit rude and is indeed bad practise, but since execv replaces the whole process it won't matter anyway.

kiw
  • 748
  • 1
  • 9
  • 18
0

Here's what I ended up doing:

  1. std::vector<std::string> to grab all the arguments I need.
  2. Creating std::vector<const char *> right before execv(), reserving enough space in it for all the arguments.
  3. Converting the second vector using std::transform().
  4. Feeding it to execv() using const_cast<char* const*>(vec.data()).

Transform pass:

std::transform(stringsVec.cbegin(), stringsVec.cend(), charsVec.begin(),
    [](const std::string &arg)
{
    return arg.c_str();
});

This is basically a combination of @ericcurtin's and @Ferruccio's answers.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Yaro Slav
  • 23
  • 5