"argv[0] = name-of-executable"是一种被广泛接受的标准还是只是一种常见的约定?

126
当在C或C++应用程序中传递参数给main()时,argv[0]是否总是可执行文件的名称?还是这只是一种常见的约定,不能保证100%的准确性?

22
在Unix上,请考虑使用以下代码:execl("/home/hacker/.hidden/malicious", "/bin/ls", "-s", (char *)0);。可执行文件的名称与argv[0]中的值无关。 - Jonathan Leffler
在Windows中,如果您像这样调用CreateProcessCreateProcess("a.exe", "/b", ...),那么argv[0]将是/b,而不是a.exe - Andry
如果第一个参数不是文件名,并且没有其他参数,那么第一个参数将为NULL,与最后一个参数相同。这将使得无法区分这两种情况,并且会导致在循环遍历argv数组或将其传递给其他函数时出现问题。 - undefined
8个回答

143
猜测(即使是有教养的猜测)很有趣,但你真的需要查阅标准文档才能确定。例如,ISO C11规定(我强调):
如果argc的值大于零,则argv[0]指向的字符串表示程序名称;如果主机环境不能提供程序名称,则argv[0][0]应是空字符。
所以,只有在程序名称可用时,它才是程序名称。而且它"表示"程序名称,不一定就是程序名称。在此之前的部分说明如下:
如果argc的值大于零,则数组元素argv[0]argv[argc-1](包括)将包含指向字符串的指针,这些字符串在程序启动前由主机环境赋予实现定义的值。
这与C99标准没有变化,这意味着甚至也不是由标准指定的——完全取决于实现。
这意味着,如果主机环境提供它,程序名称可能为空,如果主机环境提供它,则可以是任何其他内容,只要“其他内容”某种方式表示程序名称即可。在我的更加残忍的时刻,我会考虑将其翻译成斯瓦希里语,通过替换密码然后以相反的字节顺序存储它:-)。
但是,实现定义确实在ISO标准中具有特定的含义——实现必须记录其工作方式。因此,即使UNIX可以使用exec系列调用将任何内容放入argv[0]中,它也必须(并且已经)记录它。

3
这可能是标准,但Unix并没有强制执行它,你不能依赖它。 - dmckee --- ex-moderator kitten
5
这个问题根本没有提到UNIX。这只是一个简单的C问题,因此ISO C是参考文献。程序名称在标准中是实现定义的,因此实现可以自由地做任何它想做的事情,包括允许一些不是实际名称的东西存在 - 我认为我在倒数第二句已经表达清楚了。 - paxdiablo
2
Pax,我没有投票反对你,也不赞成那些这样做的人,因为这个答案是尽可能权威的。但我确实认为argv[0]值的不可靠性适用于现实世界中的编程。 - dmckee --- ex-moderator kitten
4
@caf,没错。我见过它保存各种不同的东西,比如程序的完整路径(' /progpath/prog '),只是文件名('prog'),稍微修改的名称('-prog'),描述性名称('prog - 用于进行编程的程序')以及什么都没有('')。实现需要定义它所保存的内容,但这就是标准所要求的全部。 - paxdiablo
3
谢谢大家!这个问题看起来很简单,但讨论很精彩。虽然Richard的答案适用于Unix操作系统,但我选择了paxdiablo的答案,因为我对特定操作系统的行为不太感兴趣,而主要关注已被接受的标准是否存在(或不存在)。(如果你有兴趣:在原始问题的背景下-我没有任何操作系统。我正在编写代码来构建加载到嵌入式设备上的可执行文件的原始argc / argv缓冲区,并需要知道我应该如何处理argv [0])。感谢StackOverflow的卓越表现! - Mike Willekes
显示剩余3条评论

55

在使用带有exec*()调用的Unix/Linux系统中,argv[0]将是调用者放入exec*()调用中的argv0位置的内容。

Shell使用这个约定来表示程序名,大多数其他程序也遵循相同的约定,因此argv[0]通常是程序名。

但是,一个恶意的Unix程序可以调用exec()并将argv[0]设置为任何它想要的值,所以无论C标准说什么,你不能百分之百地保证这一点。


4
这个回答比上面的paxdiablo更好。标准只是称其为“程序名”,但据我所知,没有任何地方强制执行。Unix内核会将传递给execve()的字符串不加修改地传递给子进程。 - Andy Ross
1
@Andy,你有自己的观点是自由的 :-) 但是你在执行方面是错误的。如果实现不遵循标准,则它是非符合性的。事实上,由于“程序名称”是实现定义的,例如UNIX这样的操作系统只要指定名称就是符合标准的。这包括能够通过在exec系列调用中加载任何您想要的内容来公然伪造程序名称。 - paxdiablo
这就是标准中“代表”一词的美妙之处,当它指的是argv[0](“它代表程序名称”)和argv[1..N](“它们代表程序参数”)。 “unladen swallow”是一个有效的程序名称。 - Richard Pennington
不仅仅是不受控制的Unix程序会这样做,例如sendmail也会改变argv[0]来传达其状态:https://askubuntu.com/questions/868742 - undefined

9
根据C++标准,第3.6.1节:
argv[0]应该是指向表示用于调用程序的名称的NTMBS的初始字符的指针或""。
因此,至少按照标准规定,不能保证。

6
我假设这是以空值结尾的多字节字符串? - paxdiablo

8

ISO-IEC 9899规定:

5.1.2.2.1 程序启动

如果argc的值大于零,则由argv[0]指向的字符串表示程序名称;如果程序名称无法从主机环境中获得,则argv[0][0]应为null字符。 如果argc的值大于一,则由argv[1]argv[argc-1]指向的字符串表示程序参数

我还使用了:

#if defined(_WIN32)
  static size_t getExecutablePathName(char* pathName, size_t pathNameCapacity)
  {
    return GetModuleFileNameA(NULL, pathName, (DWORD)pathNameCapacity);
  }
#elif defined(__linux__) /* elif of: #if defined(_WIN32) */
  #include <unistd.h>
  static size_t getExecutablePathName(char* pathName, size_t pathNameCapacity)
  {
    size_t pathNameSize = readlink("/proc/self/exe", pathName, pathNameCapacity - 1);
    pathName[pathNameSize] = '\0';
    return pathNameSize;
  }
#elif defined(__APPLE__) /* elif of: #elif defined(__linux__) */
  #include <mach-o/dyld.h>
  static size_t getExecutablePathName(char* pathName, size_t pathNameCapacity)
  {
    uint32_t pathNameSize = 0;

    _NSGetExecutablePath(NULL, &pathNameSize);

    if (pathNameSize > pathNameCapacity)
      pathNameSize = pathNameCapacity;

    if (!_NSGetExecutablePath(pathName, &pathNameSize))
    {
      char real[PATH_MAX];

      if (realpath(pathName, real) != NULL)
      {
        pathNameSize = strlen(real);
        strncpy(pathName, real, pathNameSize);
      }

      return pathNameSize;
    }

    return 0;
  }
#else /* else of: #elif defined(__APPLE__) */
  #error provide your own implementation
#endif /* end of: #if defined(_WIN32) */

然后您只需要解析该字符串以从路径中提取可执行文件名。


3
/proc/self/path/a.out 符号链接在 Solaris 10 及以上版本上可用。 - ephemient
1
点赞这份代码(并不意味着它是理想或正确的,例如在Windows上需要使用“GetModuleFileNameW”才能检索到任何路径,但是仅有代码的存在就构成了良好的指导)。 - Cheers and hth. - Alf

6

具有argv[0] !=可执行文件名称的应用

另请参见:https://unix.stackexchange.com/questions/315812/why-does-argv-include-the-program-name/315817

可运行的POSIX execve示例,其中argv[0] !=可执行文件名称

其他人提到了exec,但这里是一个可运行的示例。

a.c

#define _XOPEN_SOURCE 700
#include <unistd.h>

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

b.c

#include <stdio.h>

int main(int argc, char **argv) {
    puts(argv[0]);
}

然后:

gcc a.c -o a.out
gcc b.c -o b.out
./a.out

提供:

yada yada

是的,argv[0]也可能是:

在Ubuntu 16.10上测试通过。


3

这个页面说:

元素argv[0]通常包含程序名称,但不应该依赖它 - 不管怎样,一个程序不知道自己的名字是很不寻常的!

然而,其他页面似乎支持它始终是可执行文件的名称。这个页面说:

你会注意到argv[0]是程序本身的路径和名称。这允许程序发现有关自身的信息。它还将一个程序参数添加到数组中,因此在获取命令行参数时常见的错误是在想要argv[1]时抓取argv[0]。


13
有些程序利用它们不知道被调用时使用的名称这一事实。我相信 BusyBox(http://www.busybox.net/about.html)是这样工作的。它只有一个可执行文件,可以实现多个不同的命令行实用程序。它使用一堆符号链接和 argv [0] 来确定应该运行哪个命令行工具。 - Trent
是的,我记得注意到“gunzip”是指向“gzip”的符号链接,并且曾经想过那是如何工作的。 - David Thornley
2
许多程序都会查看argv [0]以获取信息。例如,如果名称的最后一个组件以短横线开头(例如“ / bin / -sh”),则shell将像登录shell一样运行配置文件和其他内容。 - Jonathan Leffler
2
@Jon:我认为登录 shell 是以 argv[0]="-/bin/sh" 开始的?无论如何,在我使用过的所有机器上都是这种情况。 - ephemient

3
我不确定这是一种普遍约定还是标准,但无论哪种情况,您都应该遵守它。我从未在Unix和类Unix系统之外看到过它被利用,尽管在Unix环境下 - 也许特别是在旧时代 - 程序可能会根据它们被调用的名称有显着不同的行为。
编辑:我从其他同时发布的帖子中看到有人将其识别为来自特定标准,但我确信这种约定早在标准之前就存在了。

1
我真希望如果有人要“打分”我的回答,他们能够给出一些关于不喜欢的原因的指示。 - Joe Mabel

1
如果您通过Workbench启动Amiga程序,则不会设置argv [0],只有通过CLI才会设置。

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