2

I am working on an assignment for my operating systems class. We have the option of using C or C++, so I decided to use C++ since I practised it on the job a bit more recently than C.

I will need to call (from " $ man execvp " on Linux)

int execvp(const char *file, char *const argv[]);

which (unless I am mistaken) means I need a C-style char* array (a string array in C), and will not be able to use std::string from C++.

My question is: what is the proper way to make/use char* arrays instead of string arrays in C++? Most people tend to say malloc is not used any more in C++ (which I tried now with some complications)

char** cmdList = (char**)malloc(128 * sizeof(char*));

but I don't know how to make a char* array without. Is it still appropriate to solve this how I would in C even though I'm using C++? I haven't ever run into a circumstance where I couldn't use string in C++.

Thanks for everyone's time.

Callum C
  • 67
  • 1
  • 1
  • 6
  • 3
    Possible duplicate of [How to convert a std::string to const char\* or char\*?](http://stackoverflow.com/questions/347949/how-to-convert-a-stdstring-to-const-char-or-char) – Ivan Aksamentov - Drop Jan 17 '16 at 21:46
  • Just be aware, that the function requires the last pointer in the array to be a nullptr – MikeMB Jan 17 '16 at 23:31

6 Answers6

3

If you put your arguments into a std::vector<std::string>, as you should in C++, then you need a small conversion to get to the char** that execvp wants. Luckily, both std::vector and std::string are continuous in memory. However, a std::vector<std::string> isn't an array of pointers, so you need to create one. But you can just use a vector for that too.

// given:
std::vector<std::string> args = the_args();
// Create the array with enough space.
// One additional entry will be NULL to signal the end of the arguments.
std::vector<char*> argv(args.size() + 1);
// Fill the array. The const_cast is necessary because execvp's
// signature doesn't actually promise that it won't modify the args,
// but the sister function execlp does, so this should be safe.
// There's a data() function that returns a non-const char*, but that
// one isn't guaranteed to be 0-terminated.
std::transform(args.begin(), args.end(), argv.begin(),
  [](std::string& s) { return const_cast<char*>(s.c_str()); });

// You can now call the function. The last entry of argv is automatically
// NULL, as the function requires.
int error = execvp(path, argv.data());

// All memory is freed automatically in case of error. In case of
// success, your process has disappeared.
Sebastian Redl
  • 69,373
  • 8
  • 123
  • 157
1

Instead of malloc use new[]:

char ** cmdlist = new char*[128];

There is no need for sizeof, since new knows the size of the type it creates. For classes this also calls the default constructor if it exists. But be careful: If there is no (public) default constructor for a type you can not use new[].

Instead of free use delete[] to release your memory when you are done:

delete[] cmdlist;

Of course, you could also use a vector. This has the advantage that the memory used to store the vector's content is automatically released when the vector is destroyed.

#include <vector>
...
std::vector<char*> cmdlist(128, nullptr);   // initialize with nullpointers
// access to entries works like with arrays
char * firstCmd = cmdList[0];
cmdlist[42] = "some command";
// you can query the size of the vector
size_t numCmd = cmdlist.size();
// and you can add new elements to it
cmdlist.push_back("a new command");
...
// the vector's internal array is automatically released
// but you might have to destroy the memory of the char*s it contains, depending on how they were created
for (size_t i = 0; i < cmdlist.size(); ++i)
    // Free cmdlist[i] depending on how it was created.
    // For example if it was created using new char[], use delete[].
maddin45
  • 737
  • 7
  • 17
  • Thank you! I see the other solutions working too, but I went with this one and managed to get things working. – Callum C Jan 17 '16 at 21:54
1

Assuming you have an std::vector<std::string> args variable that represents the argument list, you could do the following to get a C-style array of strings:

auto argvToPass = std::make_unique<const char*[]>(args.size() + 1);
int i = 0;
for (const auto& arg : args)
{
    argvToPass[i++] = arg.c_str();
}

// make we have a "guard" element at the end
argvToPass[args.size()] = nullptr;

execvp(yourFile, argvToPass.get());
user4520
  • 3,401
  • 1
  • 27
  • 50
0

You can create an array of const char* and still use strings by using string.c_str() the code will look like this

const char ** argv  = new const char*[128];
string arg1 = "arg";
argv[0] = arg1.c_str();
m7mdbadawy
  • 920
  • 2
  • 13
  • 17
0

If you want to get fancy (and safe) about it you can use std::unique_ptr to good effect to ensure everything gets properly deleted/freed in the event of an error or an exception:

// Deleter to delete a std::vector and all its
// malloc allocated contents
struct malloc_vector_deleter
{
    void operator()(std::vector<char*>* vp) const
    {
        if(!vp)
            return;

        for(auto p: *vp)
            free(p);

        delete vp;
    }
};

// self deleting pointer (using the above deleter) to store the vector of char*
std::unique_ptr<std::vector<char*>, malloc_vector_deleter> cmds(new std::vector<char*>());

// fill the vector full of malloc'd data
cmds->push_back(strdup("arg0"));
cmds->push_back(strdup("arg1"));
cmds->push_back(strdup("arg2"));

// did any of the allocations fail?
if(std::find(cmds->begin(), cmds->end(), nullptr) != cmds->end())
{
    // report error and return
}

cmds->push_back(nullptr); // needs to be null terminated

execvp("progname", cmds->data());

// all memory deallocated when cmds goes out of scope
Galik
  • 47,303
  • 4
  • 80
  • 117
  • Note that in C++11 this is no longer required; `std::unique_ptr` will call `delete[]` for arrays automatically. – user4520 Jan 17 '16 at 23:59
  • @szczurcio `std::unique_ptr` will call `delete[]` on an array, but not each of its elements. So for this I had to make a custom deleter - to delete the elements before deleting the vector. – Galik Jan 18 '16 at 02:41
  • Oh, true, I didn't see you were using `strdup`. – user4520 Jan 18 '16 at 10:20
-1

which (unless I am mistaken) means I need a C-style char* array (a string array in C), and will not be able to use std::string from C++.

No you can still use string from C++. The string class has a constructor that takes C-string:

char str[] = "a string";
string cppStr(str);

Now you can manipulate your string using the string class in C++.

user3813674
  • 2,553
  • 2
  • 15
  • 26
  • The question was not if you can get a c++ string from a `char*`, but how to get an array of `char*` (which is required by the function) from an array/vector/collection of c++ strings – MikeMB Jan 17 '16 at 23:37