Execv() and Const-Ness

execv() and const-ness

The Open Group Base Specifications explains why this is: for compatibility with existing C code. Neither the pointers nor the string contents themselves are intended to be changed, though. Thus, in this case, you can get away with const_cast-ing the result of c_str().

Quote:

The statement about argv[] and envp[] being constants is included to make explicit to future writers of language bindings that these objects are completely constant. Due to a limitation of the ISO C standard, it is not possible to state that idea in standard C. Specifying two levels of const- qualification for the argv[] and envp[] parameters for the exec functions may seem to be the natural choice, given that these functions do not modify either the array of pointers or the characters to which the function points, but this would disallow existing correct code. Instead, only the array of pointers is noted as constant.

The table and text after that is even more insightful. However, Stack Overflow doesn't allow tables to be inserted, so the quote above should be enough context for you to search for the right place in the linked document.

Can I pass a const char* array to execv?

Can I pass an array of const char pointers as the second argument?

Well yes, you already know that you can cast in order to do so.

From the POSIX documentation for execv (halfway through the Rationale section), it looks like the second argument is a char *const array only for backwards compatibility:

I wouldn't put it in those terms, but yes, there is a compatibility aspect to the chosen signature. The section you reference explains that C does not have a wholly satisfactory way to express the degree of const-ness that POSIX requires execv() to provide for the arguments. POSIX guarantees that the function will not change either the pointers in argv or the strings to which they point.

With that being the case, I think it not unreasonable to cast the argv pointer as you propose to do, though I would leave a comment in my code explaining why doing so is safe.

On the other hand, you should consider simply leaving the const off of your array declaration:

char *exe_name = "echo", *message = "You ran";
char *exe_args[] = { exe_name, message, argv[0], NULL };

Or, in your simple example, even this would do:

char *exe_args[] = { "echo", message, argv[0], "You ran", NULL };

C string literals correspond to arrays of type char, not const char, so this is perfectly legal as far as C is concerned, even though actually trying to modify the contents of those strings might fail.

On the third hand, modern C has array literals, so you could even do this:

execv("/bin/echo", (char *[]) { "echo", "You ran ", argv[0], NULL });

In that last case you don't even have a cast (the thing that resembles one is just part of the syntax for an array literal).

Why does `execvp` take a `char *const argv[]`?

To quote the page you link:

The statement about argv[] and envp[] being constants is included to
make explicit to future writers of language bindings that these
objects are completely constant. Due to a limitation of the ISO C
standard, it is not possible to state that idea in standard C.
Specifying two levels of const- qualification for the argv[] and
envp[] parameters for the exec functions may seem to be the natural
choice, given that these functions do not modify either the array of
pointers or the characters to which the function points, but this
would disallow existing correct code.

Basically the const qualification on execlp and execvp are completely compatible in the sense that they specify identical limitations on the corresponding arguments.

Is it a good idea of maintaining const-ness as much as possible?

(1) and (3) are closely related. A by-value parameter is just a local variable with that name, as is the result of your computation.

Usually it makes little difference in short functions whether you mark local variables const or not, since you can see their entire scope right in front of you. You can see whether or not the value changes, you don't need or want the compiler to enforce it.

Occasionally it does help, however, since it protects you from accidentally passing them to a function that takes its parameter by non-const reference, without realising that you're modifying your variable. So if you pass the variable as a function argument during its life, then marking it const can give you more confidence that you know what value it has afterwards.

Very occasionally, marking a variable const can help the optimizer, since you're telling it that the object is never modified, and sometimes that's true but the compiler can't otherwise prove it. But it's probably not worth doing it for that reason, because in most cases it makes no difference.

(2) is another matter. For built-in types it makes no difference, as others have explained. For class types, do not return by const value. It might seem like a good idea, in that it prevents the user writing something pointless like func_returning_a_string() += " extra text";. But it also prevents something which is pointful -- C++11 move semantics. If foo returns a const string, and I write std::string s = "foo"; if (condition) s = foo();, then I get copy assignment at s = foo();. If foo returns a non-const string then I get move assignment.

Similarly in C++03, which doesn't have move semantics, it prevents the trick known as "swaptimization" - with a non-const return value I can write foo().swap(s); instead of s = foo();.

(cpp) old conversion from constant string to char *

The warning is legitimate, because assigning a "const char*" to a "char * " is dangerous. The data pointed to can be changed, but it shouldn't.

To build the argument vector using const char*, declare the array as a char const * const[]

To pass the array to execv, cast it to char**.

This version should avoid the warning:

char const * const argVec[] = {
"texworks"
, "temp.tex"
, NULL
};
execvp("texworks", (char**)argVec);

c++ fork exec from command vector

You will want to call 'execv' so that you can make a char*[] containing the options. "ls" and "-l" each get their own slot in the array.

You'll have to cast away const, or use a char `char const*[]' array and then cast away the const on that to pass it to execv. In general, the declarations for these system calls are mildly unfriendly to C++.

See a stack overflow question on this subject.

There's a reasonable tutorial at http://www.yolinux.com/TUTORIALS/ForkExecProcesses.html.

Roughly speaking:

char * exec_args[1024];
int arg_count = 0;
std::vector<std::string> theArgs;

exec_args[arg_count++] = "/bin/whatever"; // leave command in argv[0]
for (int x = 0; x < theArgs.size(); x++) {
exec_args[arg_count++] = strdup(theArgs[x].c_str());
}
exec_args[arg_count++] = 0; // tell it when to stop!

execv("/bin/whatever", exec_args);

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

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;
}

C++ const char* To const char* const

The problem is you cannot pass const variable to function expecting non-const argument.

other word, const char * is a subset of char *.

remove the const

/*const*/ char* cmd_left[v.size()+1];

add const_cast here

cmd_left[i] = const_cast<char *>( v.at(i).c_str() );

other parts of your code look suspicious, but this will make it compile



Related Topics



Leave a reply



Submit