【问题标题】:C/C++ argv memory manageC/C++ argv 内存管理
【发布时间】:2014-10-24 17:10:13
【问题描述】:

一种标准的 C/C++ 程序格式。

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

我想知道main被调用时argv的数据在内存中是如何排列的。我从 Node.js 仓库获得了这个函数 copy_argv()。就好像内存是这样排列的:

argv_area|NULL|argv_data_area

操作系统真的以这种方式处理argv的内存吗?

就操作系统依赖而言,只讨论Linux 64bit

【问题讨论】:

  • 对于argv 数组中的元素所指向的各种字符串将如何排列,没有任何规范。我认为它们很可能是连续的,但是将它们分散在各处是完全有效的。此外,它们的内存分配不必直接遵循argv 数组分配。
  • 我对 nodejs 复制它们的方式有点惊讶。我通常会为 char** 使用一个 malloc,然后为每个 char* 使用一个 malloc。他们这样做是为了性能问题吗?
  • @Logar:是的,他们正在努力减少对malloc 的调用次数,这样既节省时间又节省空间。是否值得取决于平台。
  • @Logar,我想这可能是一些重复使用的谱系代码,有时在调用malloc 时编写被认为是昂贵的,所以代码做了一些额外的努力来避免额外的调用。也许,现在没有必要那么节俭了,但如果你已经有了节俭的代码,为什么不重用它呢。
  • 我对问题进行了一些编辑,因为我很难理解您实际问的是什么,而且它似乎取决于外部链接。如果我误解了,请回滚或进一步编辑。

标签: c++ c memory-management


【解决方案1】:

您在 Node.js 中链接的代码实际上并没有假设任何关于 argv 及其所指对象的布局。相反,您提到的布局是函数的 输出 格式。它确实像你说的那样创建了一个数组,但它不需要像那样排列输入(如果这样做,它可以只做一个memcpy())。

换句话说,你描述的特殊布局对于任何程序的 argv 来说都是足够的,但不是必需的。

Node 为什么要这样做?它就在 cmets 中:

  // Logic to duplicate argv as Init() modifies arguments
  // that are passed into it.
  char **argv_copy = copy_argv(argc, argv);

  // This needs to run *before* V8::Initialize()
  // Use copy here as to not modify the original argv:
  Init(argc, argv_copy);

为什么会这样?因为Init() 调用parseArgs(),它从 argv 中删除了它理解的几个选项,剩下的留给其他地方处理。对我来说似乎有点迂回,但是您发现的 argv 复制例程的全部目的只是为了让程序可以用作 argv,而不是对确切布局有任何特定要求他们使用。这是一个简单的布局,以后只需要一个free()

【讨论】:

    【解决方案2】:

    原始的argv 通常被处理为char * 值的单个连续块,紧接着是另一个char * 值块用于环境(int main(int argc, char **argv, char **envp)main() 变体中的envp ,也由environ 指出)。然后是参数字符串和环境字符串本身。

    参数列表和环境可能不是由malloc() 本身创建的——参数和环境是由execve() 系统调用设置的。

    三年前的某个时候,我正在玩“从 main 以外的函数中查找 argv[0]”并编写了如下所示的代码。它仍然适用于 Mac OS X Mavericks(10.9.4 — 原始测试版本是 Snow Leopard 10.6)和 Ubuntu 14.04。 (有更好但特定于平台的方法从函数中获取argv[0],但这是separate SO question,所以我不会使用这种技术,但它确实适用于一些常见的平台。)

    #include "posixver.h"
    #include <inttypes.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>     /* putenv(), setenv() */
    
    extern char **environ;  /* Should be declared in <unistd.h> */
    
    /*
    ** The object of the exercise is: given just environ (since that is all
    ** that is available to a library function) attempt to find argv[0] (and
    ** hence argc).
    **
    ** On some platforms, the layout of memory is such that the number of
    ** arguments (argc) is available, followed by the argument vector,
    ** followed by the environment vector.
    **
    **          argv                            environ
    **            |                                |
    **            v                                v
    ** | argc | argv0 | argv1 | ... | argvN | 0 | env0 | env1 | ... | envN | 0 |
    **
    ** This applies to:
    ** -- Solaris 10 (32-bit, 64-bit SPARC)
    ** -- MacOS X 10.6 (Snow Leopard, 32-bit and 64-bit)
    ** -- Linux (RHEL 5 on x86/64, 32-bit and 64-bit)
    **
    ** Sadly, this is not quite what happens on the other two Unix
    ** platforms.  The value preceding argv0 seems to be a 0.
    ** -- AIX 6.1          (32-bit, 64-bit)
    ** -- HP-UX 11.23 IA64 (32-bit, 64-bit)
    **       Sub-standard POSIX support (no setenv()) and C99 support (no %zd).
    **
    ** NB: If putenv() or setenv() is called to add an environment variable,
    ** then the base address of environ changes radically, moving off the
    ** stack onto heap, and all bets are off.  Modifying an existing
    ** variable is not a problem.
    **
    ** Spotting the change from stack to heap is done by observing whether
    ** the address pointed to by environ is more than 128 K times the size
    ** of a pointer from the address of a local variable.
    **
    ** This code is nominally incredibly machine-specific - but actually
    ** works remarkably portably.
    */
    
    typedef struct Arguments
    {
        char   **argv;
        size_t   argc;
    } Arguments;
    
    static void print_cpp(const char *tag, int i, char **ptr)
    {
        uintptr_t p = (uintptr_t)ptr;
        printf("%s[%d] = 0x%" PRIXPTR " (0x%" PRIXPTR ") (%s)\n",
                tag, i, p, (uintptr_t)(*ptr), (*ptr == 0 ? "<null>" : *ptr));
    }
    
    enum { MAX_DELTA = sizeof(void *) * 128 * 1024 };
    
    static Arguments find_argv0(void)
    {
        static char *dummy[] = { "<unknown>", 0 };
        Arguments args;
        uintptr_t i;
        char **base = environ - 1;
        uintptr_t delta = ((uintptr_t)&base > (uintptr_t)environ) ? (uintptr_t)&base - (uintptr_t)environ : (uintptr_t)environ - (uintptr_t)&base;
        if (delta < MAX_DELTA)
        {
            for (i = 2; (uintptr_t)(*(environ - i) + 2) != i && (uintptr_t)(*(environ - i)) != 0; i++)
                print_cpp("test", i, environ-i);
            args.argc = i - 2;
            args.argv = environ - i + 1;
        }
        else
        {
            args.argc = 1;
            args.argv = dummy;
        }
    
        printf("argc    = %zd\n", args.argc);
        for (i = 0; i <= args.argc; i++)
            print_cpp("argv", i, &args.argv[i]);
    
        return args;
    }
    
    static void print_arguments(void)
    {
        Arguments args = find_argv0();
        printf("Command name and arguments\n");
        printf("argc    = %zd\n", args.argc);
        for (size_t i = 0; i <= args.argc; i++)
            printf("argv[%zd] = %s\n", i, (args.argv[i] ? args.argv[i] : "<null>"));
    }
    
    static int check_environ(int argc, char **argv)
    {
        size_t n = argc;
        size_t i;
        unsigned long delta = (argv > environ) ? argv - environ : environ - argv;
        printf("environ = 0x%lX; argv = 0x%lX (delta: 0x%lX)\n", (unsigned long)environ, (unsigned long)argv, delta);
        for (i = 0; i <= n; i++)
            print_cpp("chkv", i, &argv[i]);
        if (delta > (unsigned long)argc + 1)
            return 0;
    
        for (i = 1; i < n + 2; i++)
        {
            printf("chkr[%zd] = 0x%lX (0x%lX) (%s)\n", i, (unsigned long)(environ - i), (unsigned long)(*(environ - i)),
                    (*(environ-i) ? *(environ-i) : "<null>"));
            fflush(0);
        }
        i = n + 2;
        printf("chkF[%zd] = 0x%lX (0x%lX)\n", i, (unsigned long)(environ - i), (unsigned long)(*(environ - i)));
        i = n + 3;
        printf("chkF[%zd] = 0x%lX (0x%lX)\n", i, (unsigned long)(environ - i), (unsigned long)(*(environ - i)));
        return 1;
    }
    
    int main(int argc, char **argv)
    {
        printf("Before setting environment\n");
        if (check_environ(argc, argv))
            print_arguments();
    
        //putenv("TZ=US/Pacific");
        setenv("SHELL", "/bin/csh", 1);
    
        printf("After modifying environment\n");
        if (check_environ(argc, argv) == 0)
            printf("Modifying environment messed everything up\n");
        print_arguments();
    
        putenv("CODSWALLOP=nonsense");
    
        printf("After adding to environment\n");
        if (check_environ(argc, argv) == 0)
            printf("Adding environment messed everything up\n");
        print_arguments();
    
        return 0;
    }
    

    Mac OS X 的示例输出:

    Before setting environment
    environ = 0x7FFF584D04C8; argv = 0x7FFF584D0498 (delta: 0x6)
    chkv[0] = 0x7FFF584D0498 (0x7FFF584D06B0) (./find_argv0)
    chkv[1] = 0x7FFF584D04A0 (0x7FFF584D06BD) (macedonian)
    chkv[2] = 0x7FFF584D04A8 (0x7FFF584D06C8) (obelisk)
    chkv[3] = 0x7FFF584D04B0 (0x7FFF584D06D0) (mental breakdown)
    chkv[4] = 0x7FFF584D04B8 (0x7FFF584D06E1) (testing: 1, 2, 3)
    chkv[5] = 0x7FFF584D04C0 (0x0) (<null>)
    chkr[1] = 0x7FFF584D04C0 (0x0) (<null>)
    chkr[2] = 0x7FFF584D04B8 (0x7FFF584D06E1) (testing: 1, 2, 3)
    chkr[3] = 0x7FFF584D04B0 (0x7FFF584D06D0) (mental breakdown)
    chkr[4] = 0x7FFF584D04A8 (0x7FFF584D06C8) (obelisk)
    chkr[5] = 0x7FFF584D04A0 (0x7FFF584D06BD) (macedonian)
    chkr[6] = 0x7FFF584D0498 (0x7FFF584D06B0) (./find_argv0)
    chkF[7] = 0x7FFF584D0490 (0x5)
    chkF[8] = 0x7FFF584D0488 (0x0)
    test[2] = 0x7FFF584D04B8 (0x7FFF584D06E1) (testing: 1, 2, 3)
    test[3] = 0x7FFF584D04B0 (0x7FFF584D06D0) (mental breakdown)
    test[4] = 0x7FFF584D04A8 (0x7FFF584D06C8) (obelisk)
    test[5] = 0x7FFF584D04A0 (0x7FFF584D06BD) (macedonian)
    test[6] = 0x7FFF584D0498 (0x7FFF584D06B0) (./find_argv0)
    argc    = 5
    argv[0] = 0x7FFF584D0498 (0x7FFF584D06B0) (./find_argv0)
    argv[1] = 0x7FFF584D04A0 (0x7FFF584D06BD) (macedonian)
    argv[2] = 0x7FFF584D04A8 (0x7FFF584D06C8) (obelisk)
    argv[3] = 0x7FFF584D04B0 (0x7FFF584D06D0) (mental breakdown)
    argv[4] = 0x7FFF584D04B8 (0x7FFF584D06E1) (testing: 1, 2, 3)
    argv[5] = 0x7FFF584D04C0 (0x0) (<null>)
    Command name and arguments
    argc    = 5
    argv[0] = ./find_argv0
    argv[1] = macedonian
    argv[2] = obelisk
    argv[3] = mental breakdown
    argv[4] = testing: 1, 2, 3
    argv[5] = <null>
    After modifying environment
    environ = 0x7FFF584D04C8; argv = 0x7FFF584D0498 (delta: 0x6)
    chkv[0] = 0x7FFF584D0498 (0x7FFF584D06B0) (./find_argv0)
    chkv[1] = 0x7FFF584D04A0 (0x7FFF584D06BD) (macedonian)
    chkv[2] = 0x7FFF584D04A8 (0x7FFF584D06C8) (obelisk)
    chkv[3] = 0x7FFF584D04B0 (0x7FFF584D06D0) (mental breakdown)
    chkv[4] = 0x7FFF584D04B8 (0x7FFF584D06E1) (testing: 1, 2, 3)
    chkv[5] = 0x7FFF584D04C0 (0x0) (<null>)
    chkr[1] = 0x7FFF584D04C0 (0x0) (<null>)
    chkr[2] = 0x7FFF584D04B8 (0x7FFF584D06E1) (testing: 1, 2, 3)
    chkr[3] = 0x7FFF584D04B0 (0x7FFF584D06D0) (mental breakdown)
    chkr[4] = 0x7FFF584D04A8 (0x7FFF584D06C8) (obelisk)
    chkr[5] = 0x7FFF584D04A0 (0x7FFF584D06BD) (macedonian)
    chkr[6] = 0x7FFF584D0498 (0x7FFF584D06B0) (./find_argv0)
    chkF[7] = 0x7FFF584D0490 (0x5)
    chkF[8] = 0x7FFF584D0488 (0x0)
    test[2] = 0x7FFF584D04B8 (0x7FFF584D06E1) (testing: 1, 2, 3)
    test[3] = 0x7FFF584D04B0 (0x7FFF584D06D0) (mental breakdown)
    test[4] = 0x7FFF584D04A8 (0x7FFF584D06C8) (obelisk)
    test[5] = 0x7FFF584D04A0 (0x7FFF584D06BD) (macedonian)
    test[6] = 0x7FFF584D0498 (0x7FFF584D06B0) (./find_argv0)
    argc    = 5
    argv[0] = 0x7FFF584D0498 (0x7FFF584D06B0) (./find_argv0)
    argv[1] = 0x7FFF584D04A0 (0x7FFF584D06BD) (macedonian)
    argv[2] = 0x7FFF584D04A8 (0x7FFF584D06C8) (obelisk)
    argv[3] = 0x7FFF584D04B0 (0x7FFF584D06D0) (mental breakdown)
    argv[4] = 0x7FFF584D04B8 (0x7FFF584D06E1) (testing: 1, 2, 3)
    argv[5] = 0x7FFF584D04C0 (0x0) (<null>)
    Command name and arguments
    argc    = 5
    argv[0] = ./find_argv0
    argv[1] = macedonian
    argv[2] = obelisk
    argv[3] = mental breakdown
    argv[4] = testing: 1, 2, 3
    argv[5] = <null>
    After adding to environment
    environ = 0x7FB1EA403B60; argv = 0x7FFF584D0498 (delta: 0x9ADC19927)
    chkv[0] = 0x7FFF584D0498 (0x7FFF584D06B0) (./find_argv0)
    chkv[1] = 0x7FFF584D04A0 (0x7FFF584D06BD) (macedonian)
    chkv[2] = 0x7FFF584D04A8 (0x7FFF584D06C8) (obelisk)
    chkv[3] = 0x7FFF584D04B0 (0x7FFF584D06D0) (mental breakdown)
    chkv[4] = 0x7FFF584D04B8 (0x7FFF584D06E1) (testing: 1, 2, 3)
    chkv[5] = 0x7FFF584D04C0 (0x0) (<null>)
    Adding environment messed everything up
    argc    = 1
    argv[0] = 0x107730040 (0x10772FEC0) (<unknown>)
    argv[1] = 0x107730048 (0x0) (<null>)
    Command name and arguments
    argc    = 1
    argv[0] = <unknown>
    argv[1] = <null>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-09-06
      • 2018-12-17
      • 1970-01-01
      • 2020-01-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多