获取进程的argc和argv的替代方法

时间:2016-05-03 07:26:19

标签: c++ linux windows posix bsd

我正在寻找其他方法来获取提供给流程的命令行参数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

10 个答案:

答案 0 :(得分:31)

在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 :(得分:29)

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

如果您只想使用自己的API提供对程序其他部分中命令行参数的访问,则有很多方法可以执行此操作。只需使用argv/argc中的main初始化自定义类,从那时起,您可以忽略它们并使用您自己的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<std::string>)作为容器并复制字符串。然后它们甚至可以安全地变更(如果你想要的话!)。

  

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

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

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

如果确实想要隐藏main的参数传递给Application构造函数的事实,您可以使用宏隐藏它们。

答案 2 :(得分:15)

要回答有关Windows的部分问题,可以获取命令行作为GetCommandLine函数的返回,该函数记录为here,无需显式访问{{main的参数1}}功能。

答案 3 :(得分:13)

我完全赞同@gavinb和其他人。你真的应该使用clang中的参数并存储它们或将它们传递给你需要的地方。这是唯一可移植的方式。

但是,仅出于教育目的,以下内容适用于OS X上的gcc和Linux上的#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,它将在动态链接器加载当前库时运行它。这意味着在主程序中它甚至会在$('DOMELEMENT').on('input', function() { $(this).checkvalue --> disable/enable. }); 之前运行并具有类似的环境。因此,您可以获得参数。

但请允许我重申:这仅用于教育目的,不应在任何生产代码中使用。它不会是便携式的,可能会在没有任何警告的情况下随时中断。

答案 4 :(得分: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原型。

答案 5 :(得分:5)

传递值不构成创建依赖关系。您的课程并不关心那些argcargv值的来源 - 它只是希望它们通过。您可能希望在某处复制值 - 不能保证它们不会被更改(这同样适用于GetCommandLine等替代方法。)

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

答案 6 :(得分:3)

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

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

答案 7 :(得分:2)

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

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

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

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

请参阅此SO post

答案 8 :(得分:1)

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

答案 9 :(得分:1)

最便携的方法是使用全局变量来存储参数。你可以使用Singleton(如问题中的类,但是由main初始化的单例)或类似的Service Locator来使这不那么难看,它基本上是相同的:在main中创建一个对象,静态传递并存储params,并让另一个或同一个类访问它们。

非便携式方式在Windows中使用GetCommandLine,访问/proc/<pid>/cmdline或(/proc/self/cmdline),或使用特定于编译器的扩展程序,如__attribute__((constructor))

请注意,通过等效的GetCommandLine is not possible来获取命令行(TLDR:Commandline不会传递给内核,但已经被调用过程解析并拆分(例如shell))