【问题标题】:Alternative way to obtain argc and argv of a process获取进程的 argc 和 argv 的替代方法
【发布时间】:2016-08-28 03:28:57
【问题描述】:

我正在寻找替代方法来获取提供给进程的命令行参数argcargv,而无需直接访问传递给main() 的变量。

我想创建一个独立于main() 的类,这样argcargv 就不必显式传递给使用它们的代码。

编辑:一些澄清似乎是有序的。我有这门课。

class Application
{
  int const argc_;
  char const** const argv_;

public:
  explicit Application(int, char const*[]);
};

Application::Application(int const argc, char const* argv[]) :
  argc_(argc),
  argv_(argv)
{
}

但我想要一个默认构造函数 Application::Application(),带有一些(很可能)C 代码,可以从某处提取 argcargv

【问题讨论】:

  • “获得”是什么意思?还有哪里来的?
  • 除了main 的参数外,没有可移植或标准的方法来获取程序的参数。
  • @JoachimPileborg 是的,但问题是特定于操作系统的,不可移植。
  • 您标记这个问题linuxwindowsposixbsd 并说这个问题是特定于操作系统的?如果它是特定于操作系统的,您只会提到一个操作系统,即您的目标。
  • 在正确回答问题之前,我们应该停止支持格式错误的问题。这个问题我还是不清楚,你基本上是想得到那些参数,而不需要传递它们?您可以创建一个包装器,如果注入则间接提供参数。我只是认为,一旦OP的要求明确了,我们就可以证明他想要的是否可能

标签: c++ linux windows posix bsd


【解决方案1】:

在Linux上,你可以从进程的proc文件系统中获取这些信息,即/proc/$$/cmdline

int pid = getpid();
char fname[PATH_MAX];
char cmdline[ARG_MAX];
snprintf(fname, sizeof fname, "/proc/%d/cmdline", pid);
FILE *fp = fopen(fname);
fgets(cmdline, sizeof cmdline, fp);
// the arguments are in cmdline

【讨论】:

  • 1.您可以阅读 /proc/self/cmdline 2. args 可能已被破坏 3. 使用 FILE 抽象处理 /proc 文件非常奇怪 4. ARG_MAX 只是一个参数的限制,并非所有 args 总共 5. 缺失错误检查,可以说在示例中可以省略。然而,最大的问题是 OP 可能试图做错事,如果没有进一步澄清,不应该首先发布这样的答案。
  • 我相信 /proc/*/cmdline 被截断为某个最大长度常数。这不是操作系统可以启动的进程命令行大小的限制。相反,这是在 Linux 中保留进程列表记录的限制。因此,您可以使用更长的参数列表启动进程,但内核不会为了尝试从进程列表中读取参数而记住所有参数。
  • 内核不记忆参数。相反,它存储所述参数的开始和结束地址,然后从目标进程地址空间中读取它们。我也没有看到任何代码截断结果(当然,除非请求的数量太小)。 lxr.free-electrons.com/source/fs/proc/base.c#L199
  • 尝试使用它,但只获取命令名称(如果明确指定,则带有路径)。所以cat /proc/self/cmdline x y z 返回cat/proc/self/cmdlinexyz,但该文件上的fgets 只返回cat。这是为什么呢?
  • (回答我自己) - 结果参数是NULL-separated。不能只使用字符串:(
【解决方案2】:

main 的参数由 C 运行时定义,并且是获取命令行参数的唯一标准/可移植方式。不要对抗系统。 :)

如果您只想使用您自己的 API 提供对程序其他部分的命令行参数的访问,有很多方法可以做到这一点。只需在main 中使用argv/argc 初始化您的自定义类,然后您就可以忽略它们并使用您自己的API。单例模式非常适合这类事情。

为了说明,作为最流行的 C++ 框架之一,Qt 使用了这种机制:

int main(int argc, char* argv[])
{
    QCoreApplication app(argc, argv);

    std::cout << app.arguments().at(0) << std::endl;

    return app.exec();
}

应用程序捕获参数并将其复制到QStringList。详情请见QCoreApplication::arguments()

同样,Mac 上的 Cocoa 有一个特殊的功能,它可以捕获命令行参数并使它们可用于框架:

#import <Cocoa/Cocoa.h>

int main(int argc, char *argv[])
{
    return NSApplicationMain(argc, (const char **)argv);
}

然后可以使用NSProcessInfo.arguments 属性在应用程序中的任何位置使用这些参数。

我注意到在您更新的问题中,您的班级直接在其实例中逐字存储了argc/argv 的副本:

int const argc_;
char const** const argv_;

虽然这应该是安全的(argv 指针的生命周期应该在进程的整个生命周期内有效),但它不是很像 C++。考虑创建一个字符串向量 (std::vector&lt;std::string&gt;) 作为容器并将字符串复制进去。然后它们甚至可以安全地可变(如果你愿意的话!)。

我想创建一个独立于 main() 的类,这样 argc 和 argv 就不必显式传递给使用它们的代码。

目前尚不清楚为什么从main 传递此信息在某种程度上是一件需要避免的坏事。这正是主要框架的做法。

我建议您考虑使用单例以确保您的 Application 类只有一个实例。参数可以通过main 传递,但没有其他代码需要知道或关心它们的来源。

如果你真的想隐藏main的参数被传递给Application构造函数的事实,你可以用宏隐藏它们。

【讨论】:

  • C 中的类?说不是这样!
  • 对此表示赞同。我看到用于希望 CL 访问来解析自己的参数(例如:QT)的 API 的典型方法是只要求 argv 和 argc。使用标准习语的好处是,有经验的程序员一眼就知道你在做什么。
  • 我理解使用全局的想法(它很丑,但它完成了工作),但是为什么要强制执行唯一性?允许任何人创建一组虚假的参数并传递它们有什么问题?
  • 这正是我不想要的。使用替代方法找到argcargv 可能是错误的,但为什么要劝阻人们不要这样做呢?除了我所描述的情况之外,还有一些情况似乎很有用。
  • @MatthieuM。单例只是为了保留与其封装的数据相同的语义;有一组只读参数。不是一个强烈的要求。
【解决方案3】:

为了部分回答关于 Windows 的问题,命令行可以作为 GetCommandLine 函数的返回获得,该函数记录在 here 中,而无需显式访问 main 函数的参数。

【讨论】:

  • 这是一个流行的答案。您可以考虑添加一个如何使用GetCommandLine 的示例。
【解决方案4】:

我完全同意@gavinb 和其他人的观点。你真的应该使用来自main 的参数并将它们存储或传递到你需要的地方。这是唯一的便携方式。

但是,仅出于教育目的,以下内容适用于我在 OS X 上的 clang 和在 Linux 上的 gcc

#include <stdio.h>

__attribute__((constructor)) void stuff(int argc, char **argv)
{
    for (int i=0; i<argc; i++) {
        printf("%s: argv[%d] = '%s'\n", __FUNCTION__, i, argv[i]);
    }
}

int main(int argc, char **argv)
{
    for (int i=0; i<argc; i++) {
        printf("%s: argv[%d] = '%s'\n", __FUNCTION__, i, argv[i]);
    }
    return 0;
}

将输出:

$ gcc -std=c99 -o test test.c && ./test this will also get you the arguments
stuff: argv[0] = './test'
stuff: argv[1] = 'this'
stuff: argv[2] = 'will'
stuff: argv[3] = 'also'
stuff: argv[4] = 'get'
stuff: argv[5] = 'you'
stuff: argv[6] = 'the'
stuff: argv[7] = 'arguments'
main: argv[0] = './test'
main: argv[1] = 'this'
main: argv[2] = 'will'
main: argv[3] = 'also'
main: argv[4] = 'get'
main: argv[5] = 'you'
main: argv[6] = 'the'
main: argv[7] = 'arguments'

原因是因为stuff 函数被标记为__attribute__((constructor)),它将在动态链接器加载当前库时运行它。这意味着在主程序中它甚至会在main 之前运行并且具有类似的环境。因此,您可以获得参数。

但让我再说一遍:这仅用于教育目的,不应在任何生产代码中使用。它不可移植,并且可能会在任何时间点损坏而不会发出警告。

【讨论】:

  • 这很漂亮。 __attribute__((constructor)) 函数是否保证可以访问 argv/argc,或者这是否只是滥用了表示的某些意外?
  • 完全不保证。
  • glibc 和 dyld 实现了这个扩展; musl 没有。随心所欲。
【解决方案5】:

在Windows中,如果需要获取wchar_t *的参数,可以使用CommandLineToArgvW()

int main()
{
    LPWSTR *sz_arglist;
    int n_args;
    int result;
    sz_arglist = CommandLineToArgvW(GetCommandLineW(), &n_args);
    if (sz_arglist == NULL)
    {
        fprintf(stderr, _("CommandLineToArgvW() failed.\n"));
        return 1;
    }
    else
    {
        result = wmain(n_args, sz_arglist);
    }
    LocalFree(sz_arglist);
    return result;
}

这在使用 MinGW 时非常方便,因为 gcc 无法将 int _wmain(int, wchar_t *) 识别为有效的 main 原型。

【讨论】:

    【解决方案6】:

    传递值并不构成创建依赖项。你的班级并不关心那些argcargv 值是从哪里来的——它只希望它们通过。不过,您可能希望在某处复制这些值 - 不能保证它们不会被更改(这同样适用于替代方法,如 GetCommandLine)。

    恰恰相反,事实上——当你使用像GetCommandLine 这样的东西时,你正在创建一个隐藏的依赖项。突然之间,您不再是简单的“传递值”语义,而是“神奇地从其他地方获取输入” - 结合前面提到的“值可以随时更改”,这使您的代码更加脆弱,更不用说无法测试。解析命令行参数绝对是自动化测试非常有益的情况之一。如果你愿意的话,它是一个全局变量与一个方法参数方法。

    【讨论】:

    • 但是 OP 声明这是在 C 中,所以没有类。您可以将它们传递给 init 函数,并将值存储在模块代码中的静态变量中。
    • @jamesqf 好吧,我只是使用与 OP 相同的名称 :) 传递值是重要的部分,而不是它是如何实现的。
    【解决方案7】:

    在 C/C++ 中,如果 main() 不导出它们,那么就没有直接的方法来访问它们;但是,这并不意味着没有间接的方式。许多类似 Posix 的系统使用 elf 格式,在堆栈上传递 argcargvenvp,以便由 _start() 初始化并通过正常调用约定传递到 main()。这通常在汇编中完成(因为仍然没有可移植的方法来获取堆栈指针)并放入“启动文件”中,通常具有名称 crt.o 的一些变体。

    如果您无权访问main() 以便仅导出符号,则您可能无权访问_start()。那么,为什么我还要提到它呢?因为第三个参数envp。因为environ 是一个标准的导出变量,确实_start() 期间使用envp 设置。在许多 ELF 系统上,如果您采用 environ 的基地址并使用负数组索引将其向后移动,您可以推断出 argc 和 argv 参数。第一个应该是 NULL,然后是最后一个 argv 参数,直到你到达第一个。当指向的值转换为 long 等于负索引的负数时,您有 argc 并且下一个(比负索引多一个)是 argv/argv[0]。

    【讨论】:

      【解决方案8】:

      对于我所知的需要int argc, char *argv[] 类型参数的函数,很少有常见的场景。一个明显的例子是 GLUT,它的初始化函数从main() 接管这些参数,这是一种“嵌套主”场景。这可能是也可能不是您想要的行为。如果没有,因为没有命名这些参数的约定,只要你的函数有它的参数解析器,并且你知道你在做什么,你可以做任何你需要的,硬编码:

      int foo = 1;
      char * bar[1] = {" "};
      

      或从用户输入中读取或以其他方式生成,AFAIK。

      int myFunc( int foo, char *bar[]){
      //argument parser
       {…   …}
      return 0;
      }
      

      请看这个SO post

      【讨论】:

        【解决方案9】:

        最便携的方法是使用全局变量来存储参数。您可以通过使用 Singleton (就像问题中的类,但是由 main 初始化的单例)或类似的 Service Locator 来减少丑陋,这基本上是相同的:在 main 中创建一个对象,传递和存储参数静态地,并让另一个或相同的类访问它们。

        不可移植的方式是在 Windows 中使用 GetCommandLine,访问 /proc/&lt;pid&gt;/cmdline 或 (/proc/self/cmdline),或使用编译器特定的扩展,如 __attribute__((constructor))

        请注意,通过 GetCommandLine is not possible 的等效项获取命令行功能(TLDR:命令行未传递给内核,但已由调用进程(例如 shell)解析和拆分)

        【讨论】:

          【解决方案10】:

          听起来你想要的是一个全局变量;你应该做的只是传递 argc 和 argv 作为参数。

          【讨论】:

          • 这正是我不想做的。
          • 所以你想要一个全局变量的getter,但你不想要全局变量是吗?
          【解决方案11】:

          一位讲师遇到了使用 gcc 选项 nostartfiles 然后尝试访问 argc 和 argv 的挑战。 nostartfiles 选项导致不填充 argc 和 argv。这是我对 64 位 Linux 的最佳解决方案,因为它直接使用基指针访问 argc 和 argv:

          // Compile with gcc -nostartfiles -e main args.c -o args
          #include <stdio.h>
          #include <stdlib.h>
          
          int main(int argc, char *argv[]) // argc and argv are not available when compiled with -nostartfiles
          {
              register void *rbp asm ("rbp");
          
              printf("argc is %ld\n", *(unsigned long *)(rbp + 8));
          
              for(int count = 0 ; count < *(unsigned long *)(rbp + 8) ; count++)
              {
                  printf("argv[%d] is %s\n", count, *(char **)(rbp + 16 + count * 8));
              }
          
              exit(0);
          }
          

          【讨论】:

            猜你喜欢
            • 2013-02-02
            • 1970-01-01
            • 2013-09-08
            • 2011-04-15
            • 2012-04-20
            • 2017-06-20
            • 1970-01-01
            • 1970-01-01
            • 2020-11-03
            相关资源
            最近更新 更多