我在读"操作系统:三块简单"。在第5章中,有一段代码显示了exec()系统调用的用法。
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 failedn");
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 outn");
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
为什么在第12~14行使用strdup() ?我已经尝试用以下内容替换此部分:
12 myargs[0] = "wc";
13 myargs[1] = "p3.c";
14 myargs[2] = NULL;
和前一个一样。
您的代码版本很好。与Werner的回答相反,它不会导致未定义行为。
根据POSIX规范,execvp
的函数原型为
int execvp(const char *file, char *const argv[]);
单独来看,这表明argv
数组中字符串的内容可以被修改,尽管该数组的指针元素本身不能被修改。如果是这样的话,那么确实有必要传递一个指针数组给可写的字符串,比如strdup
将提供,而不是字符串字面量,因为写入字符串字面量确实是未定义的行为。这可能是代码的作者所想的。
然而,规范的文本给了我们更多的信息:
指针的argv[]和envp[]数组以及这些数组所指向的字符串不能通过调用exec函数来修改,除非是替换进程映像的结果。
所以事实上execvp
保证不修改这些字符串。因此,传递字符串字面值是完全安全的。原始代码的作者可能不知道这个保证。
基本原理进一步解释了为什么他们选择了原型("关于argv[]和envp[]是常量的声明包含在…")。正如Werner所说,const char *const argv[]
似乎更好地表达了实际行为。然而,C的指针转换规则意味着,如果它们有,那么像
char *args[5];
args[0] = /* some writable string */;
...
execvp(file, args);
将无法编译,尽管原则上它是完全正确的。其基本原理包括礼貌地对C语言中const
的语义以及它们如何使其无法正确编写的隐晦咆哮。
可以将const char*数组传递给execv吗?
代码会随着您的更改而编译,但它可能会触发未定义的行为。
execvp
char *const argv[]
预计参数。不是char
上缺失的const
。因此,execvp
可以自由地写入传递给它的字符串。
C中的字符串字面值是不可修改的:
字符串字面值是不可修改的(实际上可以放在只读内存中,如.rodata)。如果程序试图修改由字符串字面值组成的静态数组,则该行为是未定义的。
因此,代码的作者希望安全起见,因此strdup
使用字符串字面值来获得字符串的可修改版本。
请注意c++更严格,gcc会发出
警告:ISO c++禁止将字符串常量转换为'char*' [-Wwrite-strings]
:
如果execvp
不修改参数串,那么如果它有一个表示这个(const char *const argv[]
)的签名会更好。