在 POSIX 系统上 argc 可以为零吗?

Posted

技术标签:

【中文标题】在 POSIX 系统上 argc 可以为零吗?【英文标题】:Can argc be zero on a POSIX system? 【发布时间】:2018-09-23 19:58:39 【问题描述】:

给定主程序的标准定义:

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

在 POSIX 系统上,argc 在哪些情况下可以为零?

【问题讨论】:

我会尝试使用 int execv(const char *path, char *const argv[]); 和仅包含 NULL 指针的 int execv(const char *path, char *const argv[]); 来找出答案;) C 标准允许argc< 1,如果它正好是0 我在这里找到了port70.net/~nsz/c/c11/n1570.html#5.1.2.2.1p2 鉴于广泛使用的if (argc < x) fprintf(stderr, "Usage: %s ...", argv[0]); ,我绝对看到了这个问题的实用相关性:) 如果不是因为它必须是关于 POSIX 的精确规定,这将是 ***.com/questions/8113786/… 的有效欺骗 @mtraceur argv[0] || "" 不幸的是一个int ... 【参考方案1】:

是的,这是可能的。如果你这样调用你的程序:

execl("./myprog", NULL, (char *)NULL);

或者:

char *args[] =  NULL ;
execv("./myprog", args);

那么在“myprog”中,argc 将为 0。

The standard 还特别允许使用 0 argc,如第 5.1.2.2.1 节中关于托管环境中的程序启动所述:

1 程序启动时调用的函数名为main。实现没有声明这个函数的原型。应该是 使用int 的返回类型定义并且没有参数:

int main(void)  /* ... */  

或带有两个参数(此处称为argcargv,尽管可以使用任何名称,因为它们是本地的 到声明它们的函数):

int main(int argc, char *argv[])  /* ... */ 

或等价物;或以其他一些实现定义的方式。

2 如果它们被声明,main 函数的参数应遵循以下约束:

argc 的值应为非负数。 argv[argc] 应为空指针。

...

还请注意,这意味着如果argc 为0,则argv[0] 保证为NULL。但是,标准中没有详细说明printf 在用作%s 说明符的参数时如何处理NULL 指针。在这种情况下,许多实现会输出“(null)”,但不能保证。

【讨论】:

@SylvainLeroux 您(以及其他稍后阅读此答案的人)可能会发现,除了有关一般情况的文档之外,知道几年前 one 旨在与 POSIX 兼容的系统 实际上使得无法使用argc == 0 调用程序:OpenBSD。 @mtraceur,出于好奇:如果您调用 exec 时只为程序提供路径而没有参数,OpenBSD 会做什么? (失败?复制第零个参数的路径?) @ilkkachu 在 OpenBSD 上 execve 如果您使用空的 argv 调用它,则会出现错误 EINVAL。在execve manual page 中很容易错过,因为该条件的错误行为仅在底部的可能错误列表中提及。 @mtraceur 有趣,因为 FreeBSD 允许 允许。 @mtraceur 可变参数部分中的 NULL。 0 将是一个 int 而不是指针。【参考方案2】:

是的,它可以为零,这意味着argv[0] == NULL

argv[0] 是程序名称的约定。 如果您自己启动二进制文件,您可以拥有argc == 0,就像execve 家族一样,并且不提供任何参数。您甚至可以给出一个与程序名称相去甚远的字符串。这就是为什么使用argv[0] 获取程序名称并不完全可靠的原因。

通常,您键入命令行的 shell 总是将程序名称添加为第一个参数,但同样,这是一个约定。如果argv[0] == "--help" 并且您使用getopt 解析选项,您将不会检测到它,因为optind 已初始化为1,但您可以将optind 设置为0,使用getopt 和将显示“帮助”长选项。

长话短说:完全有可能拥有argc == 0argv[0] 本身并不特别)。当启动器根本不提供参数时会发生这种情况。

【讨论】:

"但您可以将 optind 设置为 0" -- 不可移植。 POSIX 说“如果应用程序在调用 getopt () 之前将 optind 设置为零,则行为未指定。” 好吧,'--help' 是一个合法的文件名,那么,为什么 argv[0] 不能是 '--help' 呢? “这是惯例”的措辞有些不准确。不遵循“约定”的程序不符合 POSIX(阅读文档的“基本原理”部分)。因此,虽然很有可能做一些不同的事情,但这样做是不合规的。这是否是人们应该期望发生的事情(因为它是可能的)还是有争议的。我属于“不支持损坏的软件”团队。 @CharlesDuffy:这就是我们陷入标签汤(HTML 怪癖模式)、垃圾邮件(SMTP 滥用)和随机国家的常规互联网黑洞(糟糕的 BGP 推送)的原因。如果有人违反标准,并且没有历史原因允许他们这样做,那么你应该失败。大声。 @Damon 基本原理不是规范性的,这也不是该基本原理第一次提出没有得到规范性文本支持的主张。我找不到任何规范性文本,说明为程序名称传递空指针是不合格的。【参考方案3】:

添加到其他答案中,C(POSIX 与否)中没有任何内容阻止 main() 在程序中作为函数被调用。

int main(int argc, int argv[]) 
    if (argc == 0) printf("Hey!\n");
    else main(0,NULL);

    return 0;

【讨论】:

...呵呵。我认为这是特别禁止的,但事实证明只有 C++ 可以这样做,而 C 也可以。【参考方案4】:

早期的提议要求传递给 main() 的 argc 的值是 “一个或更大”。这是由草案中的相同要求驱动的 ISO C 标准。事实上,历史实现已经通过了 当没有参数提供给 exec 的调用者时,值为零 职能。此要求已从 ISO C 标准中删除,并且 随后也从本卷 POSIX.1-2017 中删除。这 措辞,特别是应该使用这个词,需要严格 使 POSIX 应用程序至少向 exec 传递一个参数 函数,从而保证调用时 argc 为 1 或更大 通过这样的应用程序。事实上,这是一种很好的做法,因为许多 现有应用程序引用 argv[0] 而不首先检查 argc 的值。

严格符合 POSIX 应用程序的要求还规定 作为第一个参数传递的值是文件名字符串 与正在启动的进程相关联。虽然现有的一些 应用程序在某些情况下传递路径名而不是文件名字符串 在这种情况下,文件名字符串更普遍有用,因为 argv[0] 的常见用法是打印诊断。在某些情况下 传递的文件名不是文件的实际文件名;例如, 登录实用程序的许多实现都使用以下约定 在实际文件名前加上 ( '-' ) 向被调用的命令解释器表明它是一个“登录 壳”。

另外,请注意 test 和 [ 实用程序需要特定的字符串 argv[0] 参数具有确定性行为 实现。

Source


在 POSIX 系统上 argc 可以为零吗?

是的,但它不会严格符合 POSIX。

【讨论】:

“POSIX 系统”是否暗示“系统上的每个程序都是'严格符合 POSIX 应用程序”?真正的问题 - 我不知道这个标准的迂腐语义。我确实知道我可以采用一个被认证为 POSIX 系统的系统并在其上简单地编写/运行一个不是“严格符合 POSIX 应用程序”的程序,我不确定这是否是预期的含义POSIX 标准是说系统在安装任何不严格符合的第三方应用程序的那一刻就变得不符合? @mtraceur 这不是一个完全独立的问题吗?不确定您是否希望在此评论线程中回答它,或者您是否希望将额外信息添加到答案中。两者看起来都很奇怪。 @mtraceur 如果你的程序在 linux 上运行,你可以期望你的程序将在 Posix 标准下运行,责任在于执行你的程序符合 Posix,也就是你的 shell。也就是说,没有什么能阻止 shell 不符合 posix 标准,但我非常怀疑有人会使用它;),如果你的 linux 发行版允许在其数据包中包含不符合标准的 Posix 程序,那么这不是 linux 的问题。 @pipe 我要求澄清这一点,因为我认为这将确定此答案中的隐含推理是否适用于该问题,或者是否具有误导性:答案“是的,但不会严格符合 POSIX”在我看来在逻辑上暗示任何可以在系统上运行 char *nothing = 0 ; execve(*prog, nothing, nothing) 的程序的存在(或可能执行)将导致 POSIX 声明该系统“不严格符合”。这让我觉得这不太可能是 POSIX 标准的意图? @Stargateur 我们真的可以期待吗?假设我们编写了一个“严格符合 POSIX 应用程序”的 C 程序,并在 Linux、FreeBSD 或最新的 HP-UX 或 Solaris 系统之一(在任何其他符合 POSIX 的 Unix 上)运行它。所以我们在 POSIX 系统上得到了我们严格遵守的 POSIX 应用程序:现在其他一些开发人员出现了,编写了一个小程序,用 execve 系统调用来调用我们的程序——但是那个开发人员犯了一个小错误,并没有给我们打电话论点:可以说整个系统不再“严格符合POSIX”吗?【参考方案5】:

TL;DR:是的,argv[0] 可以为 NULL,但不是出于我所知道的任何好的/理智的原因。但是,有理由不关心 argv[0] 是否为 NULL,并特别允许进程在它为空时崩溃。


是的,argv[0] 在 POSIX 系统上可以为 NULL,当且仅当它在没有任何参数的情况下执行。

更有趣的实际问题是,你的程序应该关心

答案是“不,你的程序可以假设 argv[0] 不是NULL”,因为一些系统实用程序(命令行实用程序)要么不在argv[0] == NULL 时以非确定性方式工作或工作,但更重要的是,没有充分的理由(除了愚蠢或邪恶的目的)为什么任何进程都会这样做。 (我不确定getopt() 的标准用法是否也会失败——但我不希望它会起作用。)

很多代码,实际上是我编写的大多数示例和实用程序,都以等价于

开头
int main(int argc, char *argv[])

    if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) 
        printf("Usage: %s [ -h | --help ]\n", argv[0]);
        /* ... print usage ... */
        return EXIT_SUCCESS;
    

这是合理并且可以接受的,因为没有充分的理由让一个进程在不提供至少正在执行的命令路径的情况下执行另一个进程,即execlp(cmd, cmd, NULL) 而不是execlp(cmd, NULL)

(但是,我可以想到一些邪恶的原因,例如利用与管道或套接字命令相关的计时竞争窗口:一个邪恶的进程通过已建立的 Unix 域套接字发送一个邪恶的请求,然后立即用授权的受害者替换自己命令(不带任何参数运行,以确保最小的启动时间),这样当服务获取请求检查对等凭据时,它会看到受害者命令,而不是原始的邪恶进程。在我看来,这是最好的让这样的受害者命令快速崩溃(SIGSEGV,通过解除对 NULL 指针的引用),而不是尝试表现得“很好”,给邪恶进程一个更大的时间窗口。)

换句话说,虽然一个进程可能将自己替换为另一个进程但没有任何参数导致argc为零,但这种行为是不合理的,严格意义上来说,存在没有已知的非恶意理由这样做。

正因为如此,以及我喜欢让邪恶和无情的程序员及其程序的生活变得艰难,我个人永远不会添加类似

的琐碎检查
static int usage(const char *argv0)

    /* Print usage using argv0 as if it was argv[0] */
    return EXIT_SUCCESS;


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

    if (argc < 1)
        return usage("(this)");
    if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
        return usage(argv[0]);

    /* argv[0] and argv[1] are non-NULL, argc >= 2 */

除非有特定现有用例的人提出要求。即使这样我也会有点怀疑,想先自己验证用例。

【讨论】:

您是否考虑检查程序以查看它们是否在编写时特别注意极端情况是一个好的/理智的/非恶意的用例?因为我经常用零参数调用我使用的每个新程序,以便快速估计在其他“不应该发生/不应该做”的情况下我可以从程序中获得什么样的行为。我什至有一个command-line tool,所以我可以快速轻松地完成它,而不必每次都为它编写代码(尽管我承认我编写的工具更多是为了控制第零个参数)。 @mtraceur:通常是的,但在这种特殊情况下不是。这仅仅是因为我认为无参数案例没有合适的用例(据我所知),但至少有一个邪恶的用例;并且恶意用例最好由程序尽早终止(并且由于 SIGSEGV 是这样做的好方法)。我不认为检查无参数情况可以表明程序在 “不应发生” 类型的情况下是否正常运行。 @mtraceur:一个例子是write()(低级 C 函数;Linux 中的系统调用包装器)返回 -1 以外的负值。它根本不应该发生,因此大多数程序员不会对其进行测试。然而,由于内核文件系统错误,它发生在 Linux 中,写入超过 2 GiB。 (目前,系统调用级别的写入限制在 2 GiB 以下,以避免在其他文件系统驱动程序中出现类似错误。)我自己的代码也是我见过的唯一一个检查这种情况的代码。 (我将其视为EIO 错误。)然而,正如我所说,我决定不在我的代码中检查argc == 0 我决定将您的回答 +1,将我们的讨论放在一边,因为我认为您的观点和方法很有价值。我希望更多的人如此批判和仔细地思考是否处理(或不处理)可能发生的所有极端情况。 如果我错了,请纠正我:如果您在 argc == 0 时取消引用 argv[0],C 标准是否真的保证任何理智的行为,更不用说尽可能早地死去?那不是取消引用空指针,这又意味着未定义的行为吗?您是否愿意相信运行您的代码所针对的每个 C 实现都会执行一些合理的操作?【参考方案6】:

只要你想运行任何像./a.out 这样的可执行文件,它都会有一个参数,那就是program name。但是在 Linux 中,可以通过在另一个调用 execv 并带有 empty argument list 的程序中执行它来运行带有 argc 的程序作为 zero

例如

int main() 
    char *buf[] =  NULL ;
    execv("./exe", buf); /* exe is binary which it run with 0 argument */
    return 0;

【讨论】:

以上是关于在 POSIX 系统上 argc 可以为零吗?的主要内容,如果未能解决你的问题,请参考以下文章

iOS:openURL 可以为零吗?

非空字符串的哈希码可以为零吗?

基于原子操作的自旋锁的 Unlock 可以直接将锁标志设置为零吗?

可以在不调用 memset 的情况下从构造函数初始化器列表中将成员结构设为零吗?

使用 C TCP 套接字,“发送”可以返回零吗?

为啥这不会崩溃?我不是在这里除以零吗?