argv[0] 什么时候可以为 null?

35
关于从命令行传递参数给main()函数的理解是,argc的最小值为1,而argv[0]将始终包含程序名称及其路径。
如果在命令行提供了参数,则argc的值将大于一,而argv[1]argv[argc-1]将包含这些参数。
现在,这个链接上的一个段落说:

argv[0]将是一个包含程序名称的字符串,或者是一个空字符串(如果不可用)。

那么,argv[0]何时会成为空字符串呢?我的意思是,程序名称及其路径总是可用的,那么它何时可能为空?
作者说“如果不可用”,但是什么情况下以及如何可能出现程序名称不可用的情况呢?

1
@ShaunHamman 目前的共识是通过“质量”来关闭:http://meta.stackexchange.com/questions/147643/should-i-vote-to-close-a-duplicate-question-even-though-its-much-newer-and-ha 由于“质量”无法衡量,我只能根据赞数来判断。;-) 可能最终取决于哪个问题在标题上命中了最佳的新手谷歌关键字。 - Ciro Santilli OurBigBook.com
4个回答

34

使用exec类的调用时,需要分别指定程序名称和可执行文件,因此可以将其设置为NULL。

但是这句话实际上来自ISO标准(可能是改写过的),该标准涵盖了从最小的微控制器到最新的z10企业级主机的各种执行环境。

许多嵌入式系统可能处于不需要可执行文件名称的情况下。

来自最新的c1x草案:

argc的值应为非负数。

argv [argc]的值应为null指针。

如果argc的值大于0,则数组成员argv [0]argv [argc-1]必须包含指向字符串的指针,在程序启动之前由主机环境给出实现定义的值。

这意味着,如果argc为零(它确实可以是),则argv [0]为NULL。

但是,即使argc不是0,您也可能无法获得程序名称,因为标准还声明:

如果argc的值大于0,则由argv [0]指向的字符串代表程序名称;如果主机环境中没有提供程序名称,则argv [0] [0]应为null字符。如果argc的值大于1,则由argv [1]argv [argc-1]指向的字符串表示程序参数。

因此,标准没有要求提供程序名称。我见过许多程序使用各种选项来设置这个值:

  • 根本不提供值(用于假装安全)。
  • 一个明显的谎言(例如,对于恶意代码,设置为sleep)。
  • 实际程序名称(例如sleep)。
  • 稍作修改的名称(例如登录shell的-ksh)。
  • 描述性名称(例如progname - 用于某事的程序)。

3
当我读到“企业级主机”的时候,我好像听到有人说:“航速一号,苏鲁先生”。 - Bob Jarvis - Слава Україні
莫里斯蠕虫还改变了它的 argv[0],如果我没记错的话。那个蠕虫有很多聪明的地方,我相信你也知道。 - undefined

8

可运行的POSIX示例代码: argv [0] == NULL

caller.c

#define _XOPEN_SOURCE 700
#include <unistd.h>

int main(void) {
    char *argv[] = {NULL};
    char *envp[] = {NULL};
    execve("callee.out", argv, envp);
}

callee.c

#include <stdio.h>

int main(int argc, char **argv) {
    if (argc == 0 && argv[0] == NULL)
        puts("yup");
}

然后:

gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o caller.out caller.c
gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o callee.out callee.c
./caller.out

输出:

yup

测试使用空参数列表的现有程序

这里有一个以路径作为参数并以无参数形式运行该命令的包装器:

caller-any.c

#include <unistd.h>
#include <stdio.h>

int main(int argc, char**argv) {
    char *empty[] = {NULL};
    execve(argv[1], empty, empty);

}

示例用法:

./caller-any.out /bin/ls

然而,GNU Coreutils工具(如ls)会检查argv[0]是否为NULL,正如在此处提到的:为什么execve系统调用可以运行“/bin/sh”而没有任何argv参数,但不能运行“/bin/ls”?,并且ls输出:

A NULL argv[0] was passed through an exec system call.
Aborted (core dumped)

在 Ubuntu 19.04 中进行了测试。


7
根据这个邮件列表,如果argc == 0,则argv[0]可能为空。但是他们没有解释何时argc可能为零。我会猜测在可执行文件不是“正常”启动(即通过命令行,popen等)的情况下,argc将为零 - 正如@paxdiablo所提到的,您可以使用exec系列函数手动设置argv,因此argc取决于这些参数而可能为零。

但是,在它们的理由部分中:

早期的建议要求传递给main()argc的值为“一或更多”。这是由ISO C标准草案中的相同要求推动的。实际上,历史实现在未向调用exec函数的程序提供参数时传递了零值。这个要求从ISO C标准中删除,随后也从IEEE Std 1003.1-2001的这一卷中删除。特别是使用“should”这个词的措辞要求严格符合POSIX应用程序在调用exec函数时传递至少一个参数,从而保证当这样的应用程序调用时argc为1或更大。实际上,这是一个好习惯,因为许多现有应用程序在首先检查argc的值之前引用argv[0]

所以您就有了:严格符合POSIX应用程序必须使argc大于零,但这绝不是保证。

关于argcargv的标准还有更多信息,请参见程序启动部分。


1
你把需求总结反了。从其他“严格符合POSIX应用程序”调用的“严格符合POSIX应用程序”可以依赖于argc>0argv [0]!= NULL。它们还必须以一种维护子进程保证的方式调用execve和相关函数。但是,没有什么可以阻止恶意用户在典型的现有POSIX系统上使用argc == 0运行您的严格符合程序。如果它是setuid或具有任何其他特殊权限,请检查argc或指针,或确保访问argv [0]时的段错误不会破坏任何内容。 - Peter Cordes

3

可以想象,在没有程序名称的平台上,程序只需在启动时加载即可。在这种情况下,我猜测argv [0]可能是NULL。 C标准允许参数计数为零,并且规定argv [argc]必须为NULL。


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