Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Invalid conversion from 'const char**' to 'char* const*'

Tags:

c++

c++11

I'm doing a school assignment where I have to call execvp, whose method signature is the following

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

However my data is in the form:

std::vector<std::string*>

I've been trying to convert said vector into the right format for the second format of execvp(), but inevitably get the following error:

command.cc:120:29: error: invalid conversion from ‘const char**’ to ‘char* const*’ [-fpermissive]
     execvp(args[0], argv);

I've tried different variations but they all lead to this error. This error confuses me, since I have no idea what it means by const*. How can you have a const*? I'd consider changing the std::vector to some other type, but this is an assignment and I'm not really allowed to change it. Below is the code I use to try and create a char*[] from the vector:

    const size_t numArgs = _simpleCommands[i]->_arguments.size();
    std::vector<const char*> args;
    for(size_t j = 0; j < numArgs; ++j)
    {
        args.push_back(strPtrToCharPtr(_simpleCommands[i]->_arguments[i]));
    }
    const char** argv = new const char*[numArgs];
    for(size_t j = 0; j < numArgs; ++j)
    {
        argv[j] = args[j];
    }
    execvp(args[0], argv);
like image 903
Malphrush Avatar asked Feb 11 '18 02:02

Malphrush


2 Answers

The char* const argv[] prototype means that argv is (the address of) an array of pointers to char, that the pointers in the array cannot be modified, but that the strings they point to can be. This is different from a char const **, which is a pointer to a pointer to char whose characters cannot be modified. Since passing it to a function that might modify the strings in the array would violate the const qualifier of const char **, it is not allowed. (You could do it with const_cast, but that would be solving the wrong problem.)

Since execvp() is a very old UNIX function and would not have the same interface today, it doesn’t have any parameter to tell the OS how many arguments there are, nor does it promise not to modify the contents of the strings in the array. You terminate the array by setting the final element to NULL.

It’s a similar format to the argv parameter of main(). In fact, it becomes the argv parameter of the main() function of the program you run, if it was written in C.

This isn’t a complete solution, since this is a homework assignment and you want to solve it on your own, but you have to create that array yourself. You can do this by creating a std::vector<char *> argv( args.size() + 1 ), setting each element but the last to the .data() pointer from the corresponding element of args, and setting the last element to NULL. Then, pass argv.data() to execvp().

Note that the POSIX.1-2008 standard says,

The argv[] and envp[] arrays of pointers and the strings to which those arrays point shall not be modified by a call to one of the exec functions, except as a consequence of replacing the process image.

Therefore, you ought to be able to get away with casting away the const-ness of the strings in the array, this once, if you don’t mind living dangerously. Normally, you would need to make a modifiable copy of each constant string in the array.

Update

Enough time has passed that I’m not giving out answers to homework. A commenter claimed that my answer did not work on g++8, which means that they didn’t implement the same algorithm I was thinking of. Therefore, posting the complete solution will be helpful.

This actually solves the closely-related problem of how to convert a std::vector<std::string> for use with execvp(). (A std::vector<std::string*> is basically never correct, and certainly not here. If you really, truly want one, change the type of s in the for loop and dereference.)

#define _XOPEN_SOURCE   700
// The next three lines are defensive coding:
#define _POSIX_C_SOURCE 200809L
#define _XOPEN_VERSION  700
#define _XOPEN_UNIX     1

#include <errno.h>
#include <stdlib.h>
#include <string>
#include <unistd.h>
#include <vector>

int main()
{
  const std::vector<std::string> cmdline{ "ls", "-al" };
  std::vector<const char*> argv;

  for ( const auto& s : cmdline ) {
    argv.push_back( s.data() );
  }
  argv.push_back(NULL);
  argv.shrink_to_fit();
  errno = 0;

 /* Casting away the const qualifier on the argument list to execvp() is safe
  * because POSIX specifies: "The argv[] [...] arrays of pointers and the
  * strings to which those arrays point shall not be modified by a call to
  * one of the exec functions[.]"
  */
  execvp( "/bin/ls", const_cast<char* const *>(argv.data()) );

  // If this line is reached, execvp() failed.
  perror("Error executing /bin/ls");
  return EXIT_FAILURE;
}

Another twist on this would be to write a conversion function that returns the std::vector<const char*> containing the command-line arguments. This is equally efficient, thanks to guaranteed copy elision. I normally like to code using RIIA and static single assignments, so I find it more elegant to return an object whose lifetime is managed automatically. In this case, the elements of argv are weak references to the strings in cmdline, so cmdline must outlive argv. Because we used C-style pointers as weak references, RIIA does not quite work here and we still need to pay attention to object lifetimes.

#define _XOPEN_SOURCE   700
#define _POSIX_C_SOURCE 200809L
#define _XOPEN_VERSION  700
#define _XOPEN_UNIX     1

#include <errno.h>
#include <stdlib.h>
#include <string>
#include <unistd.h>
#include <vector>

std::vector<const char*> make_argv( std::vector<std::string>const& in )
{
  std::vector<const char*> out;
  out.reserve( in.size() + 1 );

  for ( const auto& s : in ) {
    out.push_back( s.data() );
  }
  out.push_back(NULL);
  out.shrink_to_fit();

  return out; // Benefits from guaranteed copy elision.
}

int main()
{
  const std::vector<std::string> cmdline{ "ls", "-al" };
  errno = 0;

 /* Casting away the const qualifier on the argument list to execvp() is safe
  * because POSIX specifies: "The argv[] [...] arrays of pointers and the
  * strings to which those arrays point shall not be modified by a call to
  * one of the exec functions[.]"
  */
  execvp( "/bin/ls", const_cast<char* const *>(make_argv(cmdline).data()) );

  // If this line is reached, execvp() failed.
  perror("Error executing /bin/ls");
  return EXIT_FAILURE;
}
like image 77
Davislor Avatar answered Oct 03 '22 07:10

Davislor


I don't understand the std::vector<std::string *> part (are you sure you don't need a std::vector<std::string>?), anyway...

Rule for const: it's applied to the element on the left; if there is non element on the left, it's applied to the element on the right.

So a const char** (or char const **, if you prefer) is a pointer to a pointer to a constant char. I mean: the constant part is the char pointed, not the pointers.

And char * const * is a pointer to a constant pointer to a char; in this case the constant part is one of the two pointers, not the char pointed.

In your case the function

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

expect, as second parameter, a char * const argv[] (a C-style array of constant pointers to a char) that you can see as a char * const *.

But you call

execvp(args[0], argv);

where argv is a char const **, that is different to a char * const *.

So the error: the function expect to be able to modify the pointed char's and you pass a pointer to a pointer to not modifiable char's

And you can't define argv as a char * const *

char * cont * argv = new char * const [numArgs]; // <-- WRONG

because you can't modify it.

So, to solve the problem, I suppose you can define argv as a char **

char** argv = new char* [numArgs];

for(size_t j = 0; j < numArgs; ++j)
    argv[j] = args[j];

execvp(args[0], argv);

There ins't problem if you pass a not-constant object to a function that require a constant one (the contrary can be a problem), so you can pass a char ** to a function that expect a char * const *.

like image 45
max66 Avatar answered Oct 03 '22 05:10

max66