传递指针变量以存储字符串数组(命令行参数)

时间:2014-03-21 22:51:07

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

我一直在墙上撞了太多时间,我需要你的帮助。在我的作业中,我应该编写一个函数,将一个字符串拆分为由空格分隔的标记。这些标记被复制到动态分配的字符串数组中。该字符串作为参数传递,第二个参数是字符串数组的指针变量(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.

我一遍又一遍地阅读指针,字符串,数组和多维数组,但我似乎无法理解它。我真的不明白的一件事是,为什么指针传递为(&amp; args),为什么不把它作为数组的指针传递?我不确定我是否正在使用memcpy。

2 个答案:

答案 0 :(得分:3)

除了使用sizeof而不是strlen之类的错误之外,我们先来看看其他几个问题。


为什么 &args


  

我真的不明白的一件事是,为什么指针传递为(&amp; args),为什么不把它作为数组的指针传递?

看看你的陈述:

char **args = NULL;

这告诉你什么?

A。)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 的地址,则无法告知主要位置的主要位置。

您处理传递内容的副本

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

你的目标是:

  args          *            *
0x123e0a -> [0] 0x123fa00 -> 0x12fbae0 - 0x12fbae8 cmdline
            [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);
         Address
1. args: 0xabc00 /-> Nowhere /-> Nowhere
2. args: 0xabc00  ->    NULL   /-> Nowhere
3. args: 0xabc00  ->  0xcf324 /-> Nowhere
4. args: 0xabc00  ->  0xcf324  -> 0xedf00 - 0xedf09

重新分配

现在,如果你想要一些函数来操纵 args 指向你对该函数的贡献?您需要传递 args 指向的地址。

如果您还希望更改其指向的位置以及指向的内容,该怎么办?您需要传递指针本身的地址。这样你就可以说:&#34; args指向这里而不是那里。&#34;

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

再次:当您在 args 上执行 realloc()时,您需要使用新地址更新 args 以指向< / em>的

  

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

&#34;三维数组&#34; 。它是指针指针指针。或者根据上下文可能更简单:它是指向双指针的指针。或者如果你愿意,可以指向二维数组

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

 /* ... */

返回主 args 仍然有地址0x123400,但它不再指向NULL但是最后 realloc()的地址,例如{{1 }}


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


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

0x1234bd vs ptr->args

功能中,您可以通过以下方式访问 args

args

然而在 parse_cmdline()中有一些怪癖。首先看一下:

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

您可以使用*argv 取消引用 argv 从main获取 args 。到现在为止还挺好。但是你说:

*

这会变坏,为了理解原因,你必须看C's Precedence Table。如您所见,*argv[i] ,数组下标的优先级高于[]。因此,您索引的是 argv 而不是它指向的内容,那么您尝试取消引用索引的地址。类似的东西:

*

要解决此问题,请将解除引用封装到括号中并索引结果:

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

从简单的数学角度考虑它可能更容易:

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

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

/

的sizeof


最后,如评论中所述,(12 + 4) / 2 没有给出字符串的长度。在您的代码中,它将为您提供char指针的大小。如:指针的大小。

sizeof

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

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.)

strtok()函数将分隔符替换为memcpy((*argv)[i], token, strlen(token) + 1); | +----- Include 0x00 at end of token. ,因此您无需手动添加分隔符。这也是你可以使用 strlen()的原因。除非你事先知道长度,否则不会终止null。


工作


这是一个典型的主题,你只需要工作和工作,直到它下沉客栈。虽然是,但有更简单的方法来解决它,例如通过返回数组而不是通过ref更新它。等等,它具有教育意义,迫使你在一个层面上学习指针,论点等,让人们通过使它发挥作用来学习很多东西。

使用0x00为您提供帮助。打印很多。打印字符串的长度,变量的大小等。在这种情况下,打印地址。例如在main中:

printf()

如果你遇到困难,请退后一步。尝试传递printf("args=%p\n", (void*)&args); 而不是args并继续处理。还要尝试在函数中使用临时指针:

&args

答案 1 :(得分:0)

#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;
}