为什么这里使用了strdup()函数?

4

我正在阅读《操作系统:三个简单部分》。第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 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

为什么在第12到14行使用了strdup()函数? 我曾试图将此部分替换为以下内容:
   12         myargs[0] = "wc";                                                                                                                                                                                     
   13         myargs[1] = "p3.c";
   14         myargs[2] = NULL;

它的工作方式与以前的相同。


2
也许作者喜欢内存泄漏?不理解strdup()?没有询问他就无法确定。 - user207421
2
似乎作者不知道exec函数族如何工作。 - Some programmer dude
2
这里有一个重要的教训需要学习。你找到的90%的代码示例都会展示不良实践或者完全错误。90%可能还低估了。要保持警惕。 - William Pursell
请删除行号,只在您想讨论的行上添加注释。参见为什么代码段中没有行号? - phuclv
只有当execvp失败时才会发生泄漏,而在这种情况下,进程无论如何都会退出。 - Nate Eldredge
2个回答

2
您的代码版本很好。与Werner的回答相反,它不会导致未定义的行为。
根据 POSIX规范execvp函数的原型为:
int execvp(const char *file, char *const argv[]);

单独看这段代码,它似乎暗示了可以修改argv数组中字符串的内容,但该数组本身的指针元素不能被修改。如果是这样的话,那么确实需要传递一个可写字符串的指针数组,例如strdup提供的,而不是字符串常量,因为写入字符串常量的行为是未定义的。这可能是代码作者所考虑的。
然而,规范的文本给我们提供了更多信息:
“调用exec函数时,除了替换进程映像的结果之外,argv[]和envp[]指针数组及其指向的字符串不得被修改。”
因此,实际上execvp保证不会修改这些字符串。因此,传递字符串常量是完全安全的。原始代码的作者可能不知道这个保证。
下面的原理进一步解释了他们为什么选择了这个原型(“包含关于argv[]和envp[]是常量的声明是为了...”)。正如Werner所建议的那样,const char * const argv[]似乎更能表达实际行为。然而,C语言的指针转换规则意味着,如果他们这样做了,那么像这样的代码就会出现问题。
char *args[5];
args[0] = /* some writable string */;
...
execvp(file, args);

无法编译,尽管原则上它是完全正确的。其理由包括礼貌地掩饰了对C语言中const语义的抱怨,以及如何使其不可能正确地编写代码。
Can I pass a const char* array to execv?中还有一些进一步的讨论。

标准如何保证argv中的字符串是可变的? - user8393252
@user8393252:我不明白您所说的“可变保证”是什么意思。您可以举个例子吗?您是在讨论execvpargv参数,还是最终运行的程序接收到的argv向量? - Nate Eldredge
我只是想说,我看到了标准保证你可以更改argv中的字符串,所以当程序镜像完全被替换并且你已经传递了不可变的字符串字面量给execvp时,我对这是如何可能有点模糊不清。 - user8393252
那么你的意思是新程序接收到的argv向量?很简单:内核将传递给execvp的字符串复制到其他可写内存块中(通常在新程序的堆栈上),并使用指向这些副本的指针填充新程序的argv。由于如你所说,程序映像正在被替换,其中包括旧程序的所有内存,因此无论如何都没有办法在原地使用这些字符串。 - Nate Eldredge

1

您的更改使代码编译通过,但可能会触发未定义的行为。

execvp 需要参数 char *const argv[]。而不是在 char 上缺少的 const。因此,execvp 可以自由地写入传递给它的字符串。

C 中的字符串字面量 不能被修改:

字符串字面量是不可修改的(实际上可能会被放置在只读内存中,例如 .rodata)。如果程序尝试修改由字符串字面量形成的静态数组,则行为是未定义的。

因此,代码的作者想要保险起见,并使用 strdup 复制字符串字面量以获得可修改版本的字符串。

请注意,C++ 更为严格,在此处 gcc 将会发出警告:

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

对于您的代码。

如果execvp不修改参数字符串,那么最好有一个表达这一点的签名(const char *const argv[])。

事实上,execvp不会写入传递给它的字符串,因此这种预防措施是不必要的。POSIX规范解释了这一点,并解释了为什么他们没有将参数声明为const char *const argv[]。请参见以“关于argv[]和envp[]是常量的说明…”开头的段落。 - Nate Eldredge
请参阅以下链接了解有关C++的内容 https://dev59.com/fZbfa4cB1Zd3GeqPvZCJ 和 https://dev59.com/SHVC5IYBdhLWcg3wxEJ1。 - Nate Eldredge

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接