给定主程序的标准定义:
int main(int argc, char *argv[]) {
...
}
在什么情况下argc
POSIX系统上可以为零?
给定主程序的标准定义:
int main(int argc, char *argv[]) {
...
}
在什么情况下argc
POSIX系统上可以为零?
Answers:
是的,有可能。如果您按以下方式调用程序:
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) { /* ... */ }
或带有两个参数(尽管可以使用任何名称,因为它们在声明它们的函数中是局部的,所以在这里称为
argc
和argv
):
int main(int argc, char *argv[]) { /* ... */ }
或同等学历; 或以其他一些实现定义的方式。
2如果声明了它们,则
main
函数的参数应遵守以下约束:
- 的值
argc
应为非负数。argv[argc]
应为空指针。...
还要注意,这意味着如果argc
为0,则argv[0]
保证为NULL。但是,在标准中并未说明如何printf
将NULL指针用作%s
说明符的参数。在这种情况下,许多实现都将输出“(null)”,但我不认为这是可以保证的。
argc == 0
OpenBSD调用程序。
exec
仅使用程序的路径而不提供参数,则OpenBSD会做什么?(失败?将路径复制到第零个参数?)
是的,可以为零,表示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 == 0
(argv[0]
本身并不是很特别)。当启动器根本不给出参数时,就会发生这种情况。
早期的建议要求传递给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。
char *nothing = { 0 }; execve(*prog, nothing, nothing)
在系统上运行的任何程序的仅存在(或执行)将导致POSIX声明该系统“未严格遵循”。这使我不太可能成为POSIX标准的目标?
execve
系统调用来调用我们的程序-但是该开发人员犯了一个小错误,并且在不带任何参数的情况下调用了我们:可以说整个系统不再“严格符合POSIX”吗?
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 */
除非有特定的现有用例的人要求,否则除外。即使那样,我还是有点怀疑,想先亲自验证用例。
write()
(低级C函数; Linux中的syscall包装器)返回非-1的负值。根本不应该发生这种情况,因此大多数程序员都不会对其进行测试。但是,由于内核文件系统错误,这种情况已经在Linux中发生,可写入2 GiB以上的数据。(当前,在系统调用级别,写操作被限制在2 GiB以下,以避免在其他文件系统驱动程序中出现类似的错误。)我自己的代码也是我看到的唯一可以检查这种情况的代码。(我将其视为EIO
错误。)但是,正如我所说,我决定不检查argc == 0
代码。
argv[0]
在何时取消引用argc == 0
?那不是解引用一个空指针,这又意味着未定义的行为吗?您是否可以放心地相信,您的代码的每个C实现都会执行合理的操作?
int execv(const char *path, char *const argv[]);
用一个argv
只包含一个NULL
指针,找出;)