35

I often use the execv() function in C++, but if some of the arguments are in C++ strings, it annoys me that I cannot do this:

const char *args[4];

args[0] = "/usr/bin/whatever";
args[1] = filename.c_str();
args[2] = someparameter.c_str();
args[3] = 0;

execv(args[0], args);

This doesn't compile because execv() takes char *const argv[] which is not compatible with const char *, so I have to copy my std::strings to character arrays using strdup(), which is a pain.

Does anyone know the reason for this?

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Adam Pierce
  • 33,531
  • 22
  • 69
  • 89
  • I think you can safely use const_cast if you're sure that the program you execute doesn't change its arguments. Implementation of execv() doesn't. – Vanuan Feb 23 '12 at 20:06
  • I've been writing C++ wrappers for every system call I make so the system call throws exceptions instead of me having to check return values. As part of that, I just made a set of overloads that do all of the `const_cast`ing for me so I don't have to have them sprinkled all over the code. You need for definitions of execve for this to work because of const/non-const for both arguments. One, of course, is the system definition. The others are just inline functions that forward to that given the appropriate casts. – Omnifarious Oct 03 '17 at 20:22

4 Answers4

38

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.

C. K. Young
  • 219,335
  • 46
  • 382
  • 435
  • I'm going to accept this answer even though Jonathan's is similar. This one is worded better in my opinion. – Adam Pierce Oct 10 '08 at 05:26
  • Thanks! I just added a bit stating that people should read on, in the quoted article; it explains the rationale even better, and how, were it not for compatibility, char const* const[] would have been vastly more preferable. – C. K. Young Oct 10 '08 at 05:28
  • @user877329 The declaration lives inside ``, which only your OS provider can change. Certainly you cannot expect user code to be able to override its contents. – C. K. Young Jan 27 '14 at 05:18
  • @ChrisJester-Young. I talked to the standard commite – user877329 Jan 27 '14 at 09:54
  • @user877329 - What should be in the `#ifdef __cplusplus__` is a bunch of inline overloads that forward to the main system function after the appropriate casts. The table makes a compelling case, and that's the only way to make sure the function takes all of the different argument types that it should. – Omnifarious Oct 03 '17 at 20:24
3

const is a C++ thing - execv has taken char * arguments since before C++ existed.

You can use const_cast instead of copying, because execv doesn't actually modify its arguments. You might consider writing a wrapper to save yourself the typing.

Actually, a bigger problem with your code is that you declared an array of characters instead of an array of strings.

Try: const char* args[4];

Jonathan
  • 2,132
  • 1
  • 11
  • 8
  • Oh, I meant to type const char *, either I typed it wrong or stack overflow has chomped my asterisk. I'll edit the question. – Adam Pierce Oct 10 '08 at 05:19
  • execv will not accept const char *arg[] - at least not with g++, but thanks for the answer, I did consider casting to non-const but I did not know if that was safe. – Adam Pierce Oct 10 '08 at 05:20
  • A C compiler will allow you to pass a const char* to execv, but it will issue a warning. For a C++ compiler, though, you need to use const_cast. – Adam Rosenfield Oct 10 '08 at 05:22
  • 1
    "const is a C++ thing" -- const was a C thing, standardized in C before being standardized in C++. "execv has taken char * arguments since before C++ existed" -- but after C existed (but before C was standardized). – Windows programmer Oct 10 '08 at 05:39
  • "A C compiler will allow you to pass a const char* to execv, but it will issue a warning." <- this is equivalent to not allowing it. in C and C++, there is no difference between a warning and an error (c++ and C don't know the difference): both are diagnostics. – Johannes Schaub - litb Jul 05 '09 at 23:27
  • "in C and C++, there is no difference between a warning and an error" some warnings can be safely ignored for most of the cases (for example, deprecated conversion from const char* to char*). – Vanuan Feb 23 '12 at 20:17
  • 2
    const is not "a C++ thing". It existed in C long before C++ existed. – cmccabe Apr 07 '15 at 21:29
1

This is just a situation where C / C++ style const doesn't work very well. In reality, the kernel is not going to modify the arguments passed to exec(). It's just going to copy them when it creates a new process. But the type system is not expressive enough to really deal with this well.

A lot of people on this page are proposing making exec take "char**" or "const char * const[]". But neither of those actually works for your original example. "char**" means that everything is mutable (certainly not not true for the string constant "/usr/bin/whatever"). "const char *const[]" means that nothing is mutable. But then you cannot assign any values to the elements of the array, since the array itself is then const.

The best you could do is have a compile-time C constant like this:

const char * const args[] = {
  "/usr/bin/whatever",
  filename.c_str(),
  someparameter.c_str(),
  0};

This will actually work with the proposed type signature of "const char *const[]". But what if you need a variable number of arguments? Then you can't have a compile-time constant, but you need a mutable array. So you're back to fudging things. That is the real reason why the type signature of exec takes "const char **" for arguments.

The issues are the same in C++, by the way. You can't pass a std::vector < std::string > to a function that needs a std::vector < const std::string >. You have to typecast or copy the entire std::vector.

cmccabe
  • 4,160
  • 1
  • 24
  • 10
-2

I have usually hacked this with:

#define execve xexecve
#include <...>
#include <...>
#include <...>
#undef execve

// in case of c++
extern "C" {
    int execve(const char * filename, char ** argvs, char * const * envp);
}

;/

C. K. Young
  • 219,335
  • 46
  • 382
  • 435
Tomi
  • 21
  • 1
  • 4
    Yuk, not nice. Better to use your system's proper headers (to make sure that the type of the function the linker pulls in is what you expect, assuming the lib and headers match). Use a `const_cast<>` to do this more cleanly, rather than override everything by redefining in a horrible way with the preprocessor. – Nicholas Wilson Jan 27 '14 at 10:14
  • I think the proper C++ signature would be int execve(char const* filename, char const* const* argvs, char const* const* envp); – user877329 Apr 19 '21 at 15:55