【问题标题】:Passing a pointer variable to store an array of strings (command line arguments)传递一个指针变量来存储一个字符串数组(命令行参数)
【发布时间】:2014-04-29 12:52:37
【问题描述】:

我已经把头撞到墙上了太多小时了,我需要你的帮助。在我的作业中,我应该编写一个函数,将字符串拆分为由空格分隔的标记。这些标记被复制到动态分配的字符串数组中。字符串作为参数传递,第二个参数是指向字符串数组的指针变量(char ***argv)。我很难理解如何处理这个三维数组以及如何动态分配它。下面是相关代码:

#include <stdio.h>
#include <stdlib.h>

int main(void)
{

char **args = NULL;
char cmdline[] = "cmdline -s 20 -r -t parameter -p 20 filename";
int count = parse_cmdline(&args, cmdline);

这就是我想出的:

 #include <stdlib.h>
 #include <string.h>

 /* Parses a string into tokens (command line parameters) separated by space
 * Builds a dynamically allocated array of strings. Pointer to the array is
 * stored in variable pointed by argv.
 * 
 * Parameters:
 * argv: pointer to the variable that will store the string array
 * input: the string to be parsed (the original string can be modified, if needed)
 * 
 * Returns:
 * number of space-separated tokens in the string */
int parse_cmdline(char ***argv, char *input)
{
    int i=0;
    char *token=strtok(input," ");
    while (token!=NULL) {
        *argv=realloc(*argv,(i+1)*sizeof(char*));
        *argv[i]=malloc(sizeof(token));
        memcpy(*argv[i],token,sizeof(token));
        i++;
        token=strtok(NULL," ");
    }
    return i;
}

Valgrind 给出这个输出:

==358== Use of uninitialised value of size 8
==358==    at 0x40263B: parse_cmdline (cmdline.c:21)
==358==    by 0x40155E: test_parse_cmdline (test_source.c:19)
==358==    by 0x405670: srunner_run_all (in /tmc/test/test)
==358==    by 0x40221E: tmc_run_tests (tmc-check.c:121)
==358==    by 0x401ED7: main (test_source.c:133)
==358==  Uninitialised value was created by a stack allocation
==358==    at 0x401454: test_parse_cmdline (test_source.c:10)
==358== 
==358== Invalid write of size 8
==358==    at 0x40263B: parse_cmdline (cmdline.c:21)
==358==    by 0x40155E: test_parse_cmdline (test_source.c:19)
==358==    by 0x405670: srunner_run_all (in /tmc/test/test)
==358==    by 0x40221E: tmc_run_tests (tmc-check.c:121)
==358==    by 0x401ED7: main (test_source.c:133)
==358==  Address 0x0 is not stack'd, malloc'd or (recently) free'd
==358== 
==358== 
==358== Process terminating with default action of signal 11 (SIGSEGV)
==358==  Access not within mapped region at address 0x0
==358==    at 0x40263B: parse_cmdline (cmdline.c:21)
==358==    by 0x40155E: test_parse_cmdline (test_source.c:19)
==358==    by 0x405670: srunner_run_all (in /tmc/test/test)
==358==    by 0x40221E: tmc_run_tests (tmc-check.c:121)
==358==    by 0x401ED7: main (test_source.c:133)
==358==  If you believe this happened as a result of a stack
==358==  overflow in your program's main thread (unlikely but
==358==  possible), you can try to increase the size of the
==358==  main thread stack using the --main-stacksize= flag.
==358==  The main thread stack size used in this run was 8388608.

我一遍又一遍地阅读有关指针、字符串、数组和多维数组的内容,但我似乎无法掌握它。我真的不明白的一件事是,为什么将指针作为 (&args) 传递,为什么不将它作为数组指针传递?我也不确定我是否正确使用 memcpy。

【问题讨论】:

  • 既然这是作业,我给你一个提示。函数声明可以是int parse_cmdline(char **argv, const char* input)。根据此,尝试逆向工程传递的参数如何?
  • Sizeof() 没有给出字符串的长度。我想你想要 strlen()
  • @Mahesh:你如何建议他用那个签名更新args

标签: c pointers command-line multidimensional-array argument-passing


【解决方案1】:

除了错误地使用sizeof 而不是strlen 或类似的东西,让我们先看看其他几个问题。


为什么&amp;args


我真正不明白的一点是,为什么将指针作为 (&args) 传递,为什么不将它作为数组指针传递?

看看你的陈述:

char **args = NULL;

这告诉你什么?

答)char **args

1. char   args  =>  args is char.
2. char  *args  =>  args is pointer to char.
3. char **args  =>  args is pointer to pointer to char.

B.) char **args = NULL;

它被初始化并指向NULL。在您的函数中,您将其指向的内容从 NULL 更新为 realloc 返回的新地址。

这是非常重要的一点:你更新它所指向的内容!如果你不传递 args 的地址,你就无法告诉 main 新位置在哪里。

您处理所传递内容的副本

如果它 指向某个地址,例如从以前的 malloc 中,您可以更新它指向的内容,但您不能 realloc 因为您无法更新指针本身。也就是说:回到 main,它仍然会指向 旧位置

你的目标是这样的:

参数 * * 0x123e0a -> [0] 0x123fa00 -> 0x12fbae0 - 0x12fbae8 命令行 [1] 0x123fa08 -> 0x12fbae9 - 0x12fbaec -s [2] 0x123fa10 -> 0x12fcae0 - 0x12fcae3 20 [3] 0x123fa18 -> 0x12fbad8 - 0x12fbadb -r ...

更简单的变体

如果我们先看一个更简单的变体:

1. char  args              => args is char.
2. char *args              => args is pointer to char.
3. char *args = NULL       => args is pointer to char, pointing to NULL.
4. char *args = malloc(9); => args is pointer to char, pointing to some address.
                              returned by malloc().

即:args 在所有步骤中都有一个地址,就像任何变量一样。你总是可以说:

printf("Address of args=%p\n", (void*)&args);

上面2.、3.、4.的区别在于

  • 在第 2 点,变量没有指向任何东西。它未初始化。1
  • 在第 3 点,它被初始化,但它确实不是指向一个地址,而是 NULL。终于
  • 在第 4 点,它具有 malloc 返回的位置地址。

1.除非它是静态的、全局的等,否则它将为 NULL。


现在我们可以查看char **args = NULL

1. char **args;
2. char **args = NULL;
3. char **args = malloc(1);
4. char **args = malloc(1);
   args[0] = malloc(9);
地址 1. args: 0xabc00 /-> Nowhere /-> Nowhere 2. args: 0xabc00 ->    NULL   /-> 无处 3. 参数:0xabc00 ->  0xcf324 /-> 无处 4. 参数:0xabc00 ->  0xcf324 -> 0xedf00 - 0xedf09

重新分配

现在,如果你想要某个函数来操作 args 指向的东西,你会给那个函数什么?您需要传递 args 指向的地址。

如果您还想更改它指向的位置和指向的内容怎么办?您需要传递指针本身的地址。这样你就可以说:"args 指向这里而不是那里。"

  • 第 1 阶段:ptr 指向 A
  • 第 2 阶段:ptr 指向 B

再次:当您在 args 上执行 realloc() 时,您需要使用 新地址来更新 args 以指向。

我很难理解如何处理这个三维数组以及如何动态分配它。

不是一个“三维数组”。它是一个 pointer 到一个 pointer 到一个 pointer。或者根据上下文可能更简单:它是一个指向双指针的指针。或者如果你愿意,可以指向一个二维数组

char **args = NULL;
         |
         +-----> 0x123400 -> NULL
     parse_cmdline(&args, cmdline);
                    |
                    +-----> 0x123400 -> NULL

内部parse_cmdline()

 int parse_cmdline(char ***argv, const char* cmd)

 &argv => Address of the argument.
  argv => 0x123400 (Same address as in main)
 *argv => NULL     (Points to NULL)

 /* Inside loop: */

 /* First round: */
 *argv = realloc(*argv, (i + 1) * sizeof(char*));
   |               |
   |               +--------> Initial address NULL
   +------------------------> Updated address 0x1234af

 /* Second round */
 *argv = realloc(*argv, (i + 1) * sizeof(char*));
   |               |
   |               +--------> Old address     0x1234af
   +------------------------> Updated address 0x1234bd

 /* ... */

回到main args中仍然有地址0x123400,但它不再指向NULL而是最后一个realloc()的地址,例如0x1234bd


访问指针指针数组的指针...


现在第二个问题是如何正确访问数组的元素,假设您有一个指向指针指针变量而不是指针指针变量的指针。

ptr-&gt;argsargs

在您的 main 函数中,您可以通过以下方式访问 args

printf("%s\n", args[0]);
printf("%s\n", args[1]);

等等

但是在 parse_cmdline() 内部有一些怪癖。先看这个:

*argv

在这里,您使用* 取消引用 argv 以从 main 获取 args。到目前为止,一切都很好。但是你说:

*argv[i]

这会变坏,要了解原因,您必须查看C's Precedence Table。如您所见,数组下标[] 的优先级高于*。结果,您要索引的是 argv 而不是它指向的内容,然后您尝试取消引用索引地址。比如:

 foo = argv[i];
*foo = malloc(...);

为了解决这个问题,您将取消引用封装到括号中并索引结果:

(*argv)[i] = malloc(...);
/* and */
memcpy((*argv)[i], token, strlen(token) + 1);

用简单的数学来思考可能更容易:

12 + 4 / 2

您想将 12 和 4 的总和除以 2。由于 / 具有更高的优先级,您需要添加括号:

(12 + 4) / 2

大小


最后,正如评论中提到的,sizeof 没有给你字符串的长度。在您的代码中,它将为您提供 char 指针的大小。如:指针的大小。

char foo[32] = "ab";
int bar[64];
char *baz;

sizeof foo => 32
sizeof bar => 64 * size of int e.g. 64 * 8 = 512
sizeof baz =>  8 (or 4 or what ever the size of a pointer is in 
                  current environment.)

使用 strlen 等。您也可以出于教育目的制作自己的 strlen。当你复制时记住也要复制终止的空字节。那就是:

memcpy((*argv)[i], token, strlen(token) + 1);
                                          |
                                          +----- Include 0x00 at end of token.

strtok() 函数将分隔符替换为0x00,因此您无需手动添加它。这也是您可以使用 strlen() 的原因。如果不终止 null 就没有办法,除非你事先知道长度。


工作


这是一个典型的话题,你只需要努力工作,直到它沉没。虽然是的,但有更简单的方法来解决它,例如通过返回数组而不是通过 ref 更新它。等等,它具有教育意义,并迫使您在通过使其工作来学习很多东西的水平上学习指针、论点等。

使用printf() 来帮助您。打印很多。打印字符串的长度、变量的大小等。在这种情况下打印地址。例如在 main:

printf("args=%p\n", (void*)&args);

如果您遇到困难,请退后一步。尝试通过 args 而不是 &amp;args 并继续努力。也尝试在你的函数中有一个临时指针:

int parse_cmdline(char ***argv, char *input)
{
        int i = 0;
        char **argx = *argv; /* Temporary local. */
        char *token = strtok(input, " ");

        fprintf(stderr, "argv=%p\n", (void*)argv);

        while (token != NULL) {
                fprintf(stderr,
                        "%15s, sizeof=%u strlen=%u\n",
                        token, sizeof(token), strlen(token)
                );
                /* Now you can refer argx like you would args in main(). 
                 * as in: argx[i] instead of (*argv)[i] etc.
                 */
        }
        /* Update pointer before return. */
        *argv = argx;
        return i;
}

【讨论】:

  • 非常感谢您非常详细的回答。感谢您花时间尽可能清楚地说明问题。我已经学到了很多东西,但我会仔细阅读你的答案,直到我完全理解这个主题的一切:)
【解决方案2】:
#include <stdio.h>
#include <stdlib.h>

int parse_cmdline(char ***argv, char *input);

int main(void){
    char **args = NULL;
    char cmdline[] = "cmdline -s 20 -r -t parameter -p 20 filename";
    int count = parse_cmdline(&args, cmdline);
    for(int i = 0;i<count;++i)
        printf("%s\n", args[i]);
    printf("\n");
    return 0;
}

#include <string.h>

int parse_cmdline(char ***argv, char *input){
    int i=0;
    char *token=strtok(input," ");

    while (token!=NULL) {
        int len = strlen(token);
        *argv=realloc(*argv,(i+1)*sizeof(char*));
        (*argv)[i]=malloc(len+1);
        strcpy((*argv)[i++],token);
        token=strtok(NULL," ");
    }
    return i;
}

【讨论】:

  • 一个问题:为什么用strcpy而不是memcpy?如果我错了,请纠正我,但 (*argv)[i] 不是以 null 结尾的字符串,strcpy 期望它是。
  • @user3384360 ; strtok 替换为与分隔符匹配的 '\0' 字符。如果(*argv)[i] 不是以空值结尾的,则需要保持长度。
  • ,@user3384360 ;也已经指出,sizeof (token) 不是标记字符串的长度。这是一个指针的大小。
  • 嗯,好的,谢谢。是的,我已经改变了,我的功能现在可以工作了!如果一个数组被初始化为 char x[i];你可以使用 sizeof(x) ,对吧?我只是没有意识到这与将数组初始化为指针之间的区别。
  • @user3384360 ;好吧,例如当char a[10]; 那么sizeof(a) 的结果是10
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-10
  • 2013-10-22
相关资源
最近更新 更多