在调用exec *()系列函数时,argv的char *元素都必须是唯一的吗?

时间:2017-12-14 14:17:05

标签: c exec posix

我试图编写一个小实用程序,将其参数列表中继到exec进程,除了在构建新进程的参数列表时重复一些传入的参数。

下面是我想要做的非常简化的版本,它只是复制每个参数一次:

#include <stdlib.h>
#include <unistd.h>

#define PROG "ls"

int main(int argc, char* argv[] ) {

    int progArgCount = (argc-1)*2;
    char** execArgv = malloc(sizeof(char*)*(progArgCount+2)); // +2 for PROG and final 0
    execArgv[0] = PROG;
    for (int i = 0; i<progArgCount; ++i)
        execArgv[i+1] = argv[i/2+1];
    execArgv[progArgCount+1] = 0;

    execvp(PROG, execArgv );

} // end main()

请注意execArgv的元素的唯一性。具体来说,每个重复中的两个元素是相同的,这意味着它们指向内存中的相同地址。

标准C是否对此用法有所说明?是不正确的还是未定义的行为?如果不是,它是否仍然是不可取的,因为exec程序可能取决于其argv元素的唯一性?如果我错了,请纠正我,但程序是否可以直接修改argv个元素,因为它们是非常量的?不会造成exec程序轻易修改argv[1](比如说​​)然后访问argv[2]的风险,错误地假设这两个元素指向独立的字符串?我很确定几年前当我开始学习C / C ++时,我自己也做到了这一点,我当时并不认为argv元素可能会发生在我身上不是唯一的。

我知道执行官涉及更换过程图像&#34;但我不确定这究竟是什么。我可以想象它可能涉及深度复制给定的argv参数(在上面的例子中为execArgv)到新的内存分配,这可能会使事情无法识别,但我不太了解exec的内部功能说。这将是浪费,至少如果原始数据结构可以保留在&#34;替换&#34;操作,这是我怀疑它发生的原因。也许不同的平台/实现在这方面表现不同?请问回答者请谈谈这个吗?

我试图找到关于这个问题的文档,但我只能从http://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html找到以下内容:

  

程序使用其中一个exec函数指定的参数应传递给相应的main()参数中的新过程映像。

上述内容并未澄清它是否是传递给新流程的参数的单一深度复制。

  

参数argv是一个到​​以null结尾的字符串的字符指针数组。应用程序应确保此数组的最后一个成员是空指针。这些字符串应构成新过程映像可用的参数列表。 argv [0]中的值应指向与其中一个exec函数启动的进程相关联的文件名。

同样如上。

  

指针的argv []和envp []数组以及这些数组指向的字符串不应通过调用其中一个exec函数来修改,除非是替换过程映像。

老实说,我不知道如何解读上述内容。 &#34;更换过程映像&#34;是exec函数的全部要点!如果它要修改数组或字符串,那么这将构成在某种意义上替换过程映像&#34;的结果。这几乎意味着exec函数修改argv。这段摘录简直加剧了我的困惑。

  

包含关于argv []和envp []为常量的语句,以使未来的语言绑定编写者明确表示这些对象是完全不变的。由于ISO C标准的限制,不可能在标准C中陈述该想法。为exec函数指定argv []和envp []参数的两个const限定级别似乎是自然的选择鉴于这些函数不修改指针数组或函数指向的字符,但这将禁止现有的正确代码。相反,只有指针数组被标记为常量。从ISO C标准派生的dst = src的赋值兼容性表总结了兼容性:

不清楚是什么&#34;关于argv []和envp []的陈述是常数&#34;指;我的主要理论是它指的是文档页面顶部给出的原型中参数的const限定。但由于这些限定符只标记指针而不是char数据,因此很难明确表示这些对象是完全不变的&#34;。其次,我不知道为什么段落谈论&#34;语言绑定的作者&#34 ;;绑定到什么?这与exec函数的一般文档页面有什么关系?第三,该段的主要内容似乎是说我们坚持将char元素指向的字符串的实际argv内容保留为非const,以便向后兼容已建立的ISO C标准和现有的正确代码&#34;符合它。这由文档页面上的表格确认,我在此不会引用。这些都没有决定性地回答我的主要问题,尽管它在摘录的中间相当清楚地表明exec函数本身不会以任何方式修改给定的argv对象。

我非常感谢有关我的主要问题的信息,以及对我对引用的文档摘录的解释和理解的评论(特别是,如果我的解释是错误的以任何方式)。谢谢!

3 个答案:

答案 0 :(得分:3)

你的帖子中有很多问题,所以我只会解决它最重要的部分(IMO):

  

标准C是否对此用法有所说明?这是不正确的还是未定义的行为?

如果用“标准C”表示POSIX,那么您已经找到了exec*的规范。如果它不要求论证必须是不同的,那么它们就不需要是不同的。

正如@SomeProgrammerDude在评论中指出的那样,在字符串文字的情况下很可能会得到非独特的字符串,因为编译器可以自由地对它们进行重复数据删除(例如execl("foo", "bar", "foo"))。

  

它是否仍然不可取,因为exec'd程序可能取决于其argv元素的唯一性?

C标准本身并未强制argv中的不同字符串,因此不能依赖它们是不同的。

  

上述内容并未阐明它是否是对参数

的单一深度复制

我们可以肯定地说必须以某种方式制作副本,否则就有可能修改字符串文字(这是不允许的)。

但是,如何实现这一目标的细节似乎仍然是一种实施选择。因此,最好不要依赖任何特定的行为。

答案 1 :(得分:3)

POSIX manual中没有任何地方要求argv中的参数必须是唯一的。参数必须是以空字符结尾的字符串,并且具有空指针作为可变参数的最后一个参数:

  

arg0,...表示的参数是指向以null结尾的字符串的指针。这些字符串应构成新过程映像可用的参数列表。该列表由空指针终止。参数arg0应指向与其中一个exec函数启动的进程相关联的文件名字符串。

     

参数argv是一个到​​以null结尾的字符串的字符指针数组。应用程序应确保此数组的最后一个成员是空指针。这些字符串应构成新过程映像可用的参数列表。 argv [0]中的值应指向与其中一个exec函数启动的进程相关联的文件名字符串。

这是POSIX所需的所有。因此,没有明确要求参数必须是唯一的。因此,如果实现要求参数是唯一的,则与标准冲突。因为标准函数不能强加未指定的要求或者没有在标准中指定的效果。

  

“替换过程映像”是exec函数的全部内容!如果它要修改数组或字符串,那么在某种意义上,这将构成“替换过程映像的结果”。这几乎意味着exec函数将修改argv。

成功时只允许修改 ;否则,“替换图像”不会发生,因此没有“后果”。它本质上是为了防止argvenvp在原始进程中的失败的exec调用中处于不可用状态。

exec不能执行浅拷贝,因为它无法知道它给出的参数的存储持续时间。所以即使以下情况也应该没问题:

char *p = "argument";
execvp("cmd", (char *[]){"cmd", p, p + 2, (char*)0});

答案 2 :(得分:2)

  

标准C是否对此用法有所说明?这是不正确的还是未定义的行为?

如果两个指针指向同一个内存位置,则没有问题。这不是未定义的行为。

  

如果不是,它是否仍然不可取,因为exec'd程序可能依赖于其argv元素的唯一性?

POSIX标准没有指定任何关于argv元素的唯一性。

  

如果我错了,请纠正我,但是程序不能直接修改他们的argv元素,因为它们是非const的吗?

来自C Standards#5.1.2.2.1p2

The parameters argc and argv and the strings pointed to by the argv array shall be modifiable by the program, and retain their last-stored values between program startup and program termination.

所以答案是 - 是的,这是可能的。

  

这不会造成执行程序轻微修改argv [1](比如说​​)然后访问argv [2]的风险,错误地假设这两个元素指向独立字符串吗?

在计算中,exec是操作系统的一项功能,它在已有进程的上下文中运行可执行文件,替换以前的可执行文件。

因此,当执行exec族系统调用时,参数中给出的程序将被加载到调用者的地址空间并覆盖该程序。因此,一旦指定的程序文件开始执行,调用者的地址空间中的原始程序就会消失,并被新程序和存储在新替换的地址空间中的参数列表argv所取代。

POSIX标准说:

The number of bytes available for the new process' combined argument and environment lists is {ARG_MAX}. It is implementation-defined whether null terminators, pointers, and/or any alignment bytes are included in this total.

ARG_MAX

{ARG_MAX} Maximum length of argument to the exec functions including environment data.

这意味着为新的进程参数分配了一些空间,并且可以安全地假设参数字符串被复制到该空间。

  

我知道exec'ing涉及“替换过程图像”,但我不确定这究竟是什么。

检查this

  

也许不同的平台/实现在这方面表现不同?请问回答者请谈谈这个吗?

实现可能因平台而异,但Unix的所有变体必须遵循相同的POSIX标准才能保持兼容性。所以,我相信所有平台上的行为都必须相同。