4

I am reading "Operating System: Three Easy Pieces". In chapter 5, there is a piece of code that show the usage of exec() system call.

    1 #include "common.h"
    2
    3 int main(int argc, char ** argv) {
    4     printf("hello world (pid: %d)\n", (int) getpid());
    5     int rc = fork();
    6     if (rc < 0) {
    7         fprintf(stderr, "fork failed\n");
    8         exit(1);
    9     } else if (rc == 0) {
   10         printf("hello, I am child (pid: %d)\n", (int) getpid());
   11         char *myargs[3];
   12         myargs[0] = strdup("wc");
   13         myargs[1] = strdup("p3.c");
   14         myargs[2] = NULL;
   15         execvp(myargs[0], myargs); // run work count
   16         printf("this shouldn't print out\n");
   17     } else {
   18         int wc = wait(NULL);
   19         printf("hello, I am parent of %d (wc: %d) (pid: %d)\n", rc, wc, (int) getpid());
   20     }
   21     return 0;
   22 }
   23

Why is strdup() used in line 12~14 ? I've tried to replace this part with the following:

   12         myargs[0] = "wc";                                                                                                                                                                                     
   13         myargs[1] = "p3.c";
   14         myargs[2] = NULL;

It works just as the previous one.

wuzirui
  • 41
  • 3
  • 2
    Maybe the author liked memory leaks? Didn't understand `strdup()`? Impossible to say without asking him. – user207421 Oct 09 '21 at 09:07
  • 2
    It seems the author doesn't know how the `exec` family of functions work. – Some programmer dude Oct 09 '21 at 09:09
  • 2
    There is an important lesson to be learned here. 90% of the code samples you find will exhibit bad practice or be flat-out wrong. 90 is probably on the low side. Be wary. – William Pursell Oct 09 '21 at 11:05
  • please remove the line numbers. Just put comments on the line that you want to talk about. See [Why is there no line numbering in code sections?](https://meta.stackoverflow.com/q/252559/995714) – phuclv Oct 09 '21 at 13:29
  • It's only a leak if `execvp` fails, and in that case the process just exits anyway. – Nate Eldredge Oct 09 '21 at 16:35

2 Answers2

2

Your version of the code is fine. Contrary to Werner's answer, it will not cause undefined behavior.

According to the POSIX spec, the function prototype for execvp is

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

Taken all by itself, this would suggest that the contents of the strings in the argv array could be modified, although the pointer elements of that array themselves could not. If that were so then it would indeed be necessary to pass an array of pointers to writable strings, such as strdup would provide, and not string literals, since writing to a string literal is indeed undefined behavior. This may have been what the author of the code was thinking.

However, the text of the spec gives us more information:

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.

So in fact execvp is guaranteed not to modify those strings. As such it is perfectly safe to pass a string literal. The author of the original code might not have known about this guarantee.

The rationale further down explains why they chose the prototype as they did ("The statement about argv[] and envp[] being constants is included to..."). As Werner suggests, const char *const argv[] would seem to better express the actual behavior. However, C's pointer conversion rules mean that if they had, then code like

char *args[5];
args[0] = /* some writable string */;
...
execvp(file, args);

would not compile, even though in principle it is perfectly fine. The rationale includes a politely veiled rant about the semantics of const in C and how they make it impossible to write this properly.

There is some further discussion at Can I pass a const char* array to execv?.

Nate Eldredge
  • 48,811
  • 6
  • 54
  • 82
  • How is it that the strings in `argv` are guaranteed to be mutable by the standard then? – user8393252 Apr 16 '23 at 00:26
  • @user8393252: I don't understand what you mean by "guaranteed mutable". Can you give an example? Are you talking about the `argv` argument to `execvp`, or the `argv` vector received by the program that is eventually run? – Nate Eldredge Apr 17 '23 at 02:31
  • I just meant that I’ve seen the standard guarantees you can change the strings in `argv`, so it is a bit ambiguous to me how that’s possible when the program image is replaced entirely and you’ve passed string literals (that are not mutable) to `execvp` – user8393252 Apr 17 '23 at 02:35
  • So you mean the `argv` vector received by the new program? It's very simple: the kernel copies the strings passed to `execvp` into some other block of writable memory (normally on the stack of the new program), and populates the new program's `argv` with pointers to the copies. Since as you say the program image is being replaced, which includes all the memory of the old program, there would be no way for those strings to be used "in place" anyhow. – Nate Eldredge Apr 17 '23 at 02:39
1

The code compiles with your change, but it might trigger undefined behaviour.

execvp expects a parameter char *const argv[]. Not the missing const on the char. So execvp is free to write to the strings passed to it.

String literals in C are not modifiable:

String literals are not modifiable (and in fact may be placed in read-only memory such as .rodata). If a program attempts to modify the static array formed by a string literal, the behavior is undefined.

So the author of the code wants to be on the safe side and strdups the string literals to get a modifiable version of the string.

Please note that C++ is more strict and there gcc would emit

warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]

for your code.

If execvp does not modify the arg strings, then it would be better if it had a signature that expressed this (const char *const argv[]).

Werner Henze
  • 16,404
  • 12
  • 44
  • 69
  • In fact `execvp` does not write to the strings passed to it, so this precaution is unnecessary. The [POSIX spec](https://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html) explains this, and also explains why they didn't declare the argument as `const char *const argv[]`. See the paragraph starting "The statement about argv[] and envp[] being constants..." – Nate Eldredge Oct 09 '21 at 16:33
  • See also https://stackoverflow.com/questions/36925388/can-i-pass-a-const-char-array-to-execv, and https://stackoverflow.com/questions/190184/execv-and-const-ness for C++. – Nate Eldredge Oct 09 '21 at 16:40