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


78

给定主程序的标准定义:

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

在什么情况下argcPOSIX系统上可以为零?


12
我想尝试int execv(const char *path, char *const argv[]);用一个argv只包含一个NULL指针,找出;)

4
C标准允许argc使用< 1,如果正是0我在这里找到的话port70.net/~nsz/c/c11/n1570.html#5.1.2.2.1p2
Achal

13
鉴于被广泛使用if (argc < x) { fprintf(stderr, "Usage: %s ...", argv[0]); },我绝对可以看到这个问题的实际意义:)

3
如果不是因为它必须关于POSIX的确切规定,将是stackoverflow.com/questions/8113786/…的有效副本
underscore_d

5
argv[0] || ""不幸的是,@ mtraceur是一个完整的...

Answers:


94

是的,有可能。如果您按以下方式调用程序:

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

或替代地:

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

然后在“ myprog”中argc将为0。

该标准还专门允许argc在主机环境中有关程序启动的第5.1.2.2.1节中所述的0 :

1在程序启动时调用的函数名为main。该实现没有为此函数声明任何原型。它的返回类型应为int且不带参数:

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

或带有两个参数(尽管可以使用任何名称,因为它们在声明它们的函数中是局部的,所以在这里称为argcargv):

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

或同等学历; 或以其他一些实现定义的方式。

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

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

...

还要注意,这意味着如果argc为0,则argv[0]保证为NULL。但是,在标准中并未说明如何printf将NULL指针用作%s说明符的参数。在这种情况下,许多实现都将输出“(null)”,但我不认为这是可以保证的。


6
@(SylvainLeroux)您(和其他稍后阅读此答案的人)可能会发现,除了有关一般案例的文档外,有趣的是,几年前,一个旨在与POSIX兼容的系统实际上已经实现了。无法使用argc == 0OpenBSD调用程序。
mtraceur '18

@mtraceur,出于好奇:如果exec仅使用程序的路径而不提供参数,则OpenBSD会做什么?(失败?将路径复制到第零个参数?)
ilkkachu

1
@ilkkachu在OpenBSD上execve失败,EINVAL如果您将其调用为空,则会出现错误argv。在execve手册页中很容易错过,因为仅在底部的可能错误列表中提到了该条件的错误行为。
mtraceur '18

@mtraceur有趣,因为FreeBSD的确实允许它。
dbush

1
@mtraceur varargs部分中的NULL。0将是一个int而不是指针。
Antti Haapala

22

为了增加其他答案,C语言中没有任何内容(无论是否存在POSIX)都不能阻止main()作为程序内的函数被调用。

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

    return 0;
}

10
嗯 我以为这是特别禁止的,但事实证明只有C ++可以做到这一点,而C可以​​做到这一点。
Daniel H

19

是的,可以为零,表示argv[0] == NULL

约定argv[0]是程序的名称。你可以有argc == 0你自己发起的二进制,像execve的家庭,不给任何说法。您甚至可以提供一个与程序名称相距甚远的字符串。这就是为什么argv[0]用来获取程序名称并不完全可靠的原因。

通常,您在命令行中键入的shell始终将程序名称添加为第一个参数,但这又是一个约定。如果argv[0]==“ --help”并使用getopt to parse选项,则不会将其检测到,因为已将其optind初始化为1,但可以将其设置optind为0,请使用getopt并显示“ help” long选项。

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


“但是您可以将optind设置为0” –不可移植。POSIX说:“如果应用程序在调用getopt()之前将optind设置为零,则行为未指定。”

6
好吧,“-help”是合法的文件名,因此,为什么argv [0]不能为“ --help”?
汤姆(Tom)

1
“这是一个惯例”的措词有点不准确。不遵循该“约定”的程序不符合POSIX(请阅读文档的“ Rationale”部分)。因此,尽管有可能做一些不同的事情,但这样做是不合规的。这是否是人们应该期望发生的事情(因为有可能),这取决于争论。我在“不支持损坏的软件”团队中。
戴蒙

2
@CharlesDuffy:这就是我们被标签汤(HTML怪癖模式),电子邮件垃圾邮件(SMTP滥用)和随机国家的常规互联网黑洞(不良BGP推送)困住的原因。如果有人违反标准,并且没有历史原因允许他们这样做,那么您应该失败。高声。
凯文

1
@Damon基本原理不是规范性的,也不是第一次没有得到规范性文本支持的主张。我找不到任何规范性文本,这些文本说传递程序名称的空指针不符合要求。

15

早期的建议要求传递给main()的argc的值必须为“一个或更大”。这是由ISO C标准草案中的相同要求驱动的。实际上,当没有参数提供给exec函数的调用者时,历史实现的值传递为零。此要求已从ISO C标准中删除,随后也从POSIX.1-2017的此卷中删除。措词(特别是单词的使用)应要求严格符合POSIX应用程序的要求,至少将一个参数传递给exec函数,从而保证argc在此类应用程序调用时为一个或多个。实际上,这是一个好习惯,因为许多现有应用程序在未首先检查argc值的情况下引用了argv [0]。

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

另外,请注意,test和[实用程序要求argv [0]参数使用特定的字符串,以便在所有实现中都具有确定性的行为。

资源


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

是的,但并不严格符合POSIX。


2
“ POSIX系统”是否意味着“系统上的每个程序都是“严格符合POSIX的应用程序”?真正的问题-在这个问题上,我不知道该标准的学究语义。我确实知道我可以采用经认证为POSIX系统的系统,并在该系统上编写/运行不是“严格符合POSIX应用程序”的程序,而且我不确定这是否是预期的含义。 POSIX标准是说系统上一旦安装了任何不严格遵从的第三方应用程序就变得不遵从?
mtraceur '18

@mtraceur这不是一个完全独立的问题吗?不知道您是否希望在此评论主题中得到答复,或者是否要在答案中添加其他信息。两者似乎都很奇怪。
管道

@mtraceur如果您的程序在linux上运行,则可以期望您的程序将在Posix标准下运行,责任是使执行您的程序符合Posix的规范,也就是您的shell。就是说,没有什么可以防止shell不符合posix的要求,但是我非常怀疑有人会使用它;),如果您的linux发行包中的数据包中包含不一致的Posix程序,那不是linux的问题。
Stargateur

@pipe我要求澄清这一点,因为我认为它将确定该答案中的隐式推理是否适用于该问题,或者是否具有误导性:答案“是,但并非严格符合POSIX”在我看来从逻辑上暗示,可以char *nothing = { 0 }; execve(*prog, nothing, nothing)在系统上运行的任何程序的仅存在(或执行)将导致POSIX声明该系统“未严格遵循”。这使我不太可能成为POSIX标准的目标?
mtraceur '18

@Stargateur我们真的可以期望吗?假设我们编写了一个“严格符合POSIX应用程序”的C程序,并在Linux或FreeBSD或最新的HP-UX或Solaris系统之一(在任何其他符合POSIX的Unix上)上运行它。因此,我们已经在POSIX系统上获得了严格符合POSIX的应用程序:现在出现了其他一些开发人员,编写了一个小程序,该程序通过execve系统调用来调用我们的程序-但是该开发人员犯了一个小错误,并且在不带任何参数的情况下调用了我们:可以说整个系统不再“严格符合POSIX”吗?
mtraceur '18

3

每当您想要运行任何可执行文件时,./a.out它都会带有一个参数program name。但是,它是可能的运行程序argczero在Linux中,通过从其他程序执行它的呼叫execvempty argument list

例如

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

3

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 */

除非有特定的现有用例的人要求,否则除外。即使那样,我还是有点怀疑,想先亲自验证用例。


您是否考虑检查程序,以确保它们在编写时充分注意了转角案例是一个好/合理/非恶意的用例?因为我会定期使用零参数调用我使用的每个新程序,以便快速估计在“不应该发生/不应该做”的其他情况下我可以从程序中期望什么样的行为。我什至有一个命令行工具,因此我可以快速轻松地做到这一点,而不必每次都为它编写代码(尽管坦率地说,我编写该工具更多是为了控制第零个参数)。
mtraceur '18

@mtraceur:通常是,但是在这种情况下,不是。这仅仅是因为我认为无参数用例没有适当的用例(据我所知),但是至少有一个有害的用例。并且最好尽快通过程序死掉来消除邪恶的用例(由于SIGSEGV是一种很好的实现方式)。我认为检查无参数的情况并不表示该程序是否在“不应发生”的情况下表现出合理的行为。
Nominal Animal

@mtraceur:一个示例是write()(低级C函数; Linux中的syscall包装器)返回非-1的负值。根本不应该发生这种情况,因此大多数程序员都不会对其进行测试。但是,由于内核文件系统错误,这种情况已经在Linux中发生,可写入2 GiB以上的数据。(当前,在系统调用级别,写操作被限制在2 GiB以下,以避免在其他文件系统驱动程序中出现类似的错误。)我自己的代码也是我看到的唯一可以检查这种情况的代码。(我将其视为EIO错误。)但是,正如我所说,我决定不检查argc == 0代码。
Nominal Animal

除了我们的讨论之外,我已经决定为您的答案+1,因为我认为您的观点和方法很有价值。我希望更多的人如此批判和谨慎地考虑是否处理(或不处理)可能发生的所有紧急情况。
mtraceur '18

如果我错了,请纠正我:C标准实际上可以保证任何理智的行为,更不用说尽早死亡,如果您argv[0]在何时取消引用argc == 0?那不是解引用一个空指针,这又意味着未定义的行为吗?您是否可以放心地相信,您的代码的每个C实现都会执行合理的操作?
mtraceur '18
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.