【问题标题】:iterate a dynamically allocated char array迭代一个动态分配的 char 数组
【发布时间】:2018-02-03 20:10:39
【问题描述】:

我是 C 中指针的新手,请多多包涵。我正在尝试编写一个函数,如果找到字符串则返回数组的索引,否则存储字符串然后返回索引。我想创建一个像 {"1234", "3241", "2234", "2222"} 这样的结构,为此我动态分配了一个这样的 char 数组(我需要从命令行获取大小)

char** userToRdrMap = {NULL};
userToRdrMap = malloc(numOfReducers * sizeof(char*));

现在,我有一个 userId 的数组

char *userId[numOfReducers];
    userId[0] = "2234";
    userId[1] = "2222";
    userId[2] = "3223";
    userId[3] = "2222";

对于每个 userId,我将调用一个函数findRdrNum(userId[i]),它将检查之前动态分配的字符数组userToRdrMap,如果存在 userId,它应该返回索引,否则存储它然后返回索引。 我无法继续解决这个问题,因为我对指针感到困惑和迷失。有人可以帮忙吗? 这是我的程序,它尝试返回索引然后存储它。

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

int numOfReducers;
char** userToRdrMap = {NULL};
char* temp1;

int findRdrNum(char *userId){
    if(!userToRdrMap[0]){
        return 0;
    } else{
        int i;
        printf("%s", userToRdrMap[1]);
        for(i=0; i<numOfReducers; i++){
            if(strcmp(userToRdrMap[i], userId))
                return i;
        }
    }
    return NULL;
}

int main (int argc, char *argv[])
{
    numOfReducers = atoi(argv[1]);
    int i;
    char *userId[numOfReducers];
    userId[0] = "2234";
    userId[1] = "2222";
    userId[2] = "3223";
    userId[3] = "2222";
    int rdrNum;

    userToRdrMap = malloc(numOfReducers * sizeof(char*));
/*  temp1 = malloc(numOfReducers * 4 * sizeof(char));
    for (i = 0; i < numOfReducers; i++) {
      userToRdrMap[i] = temp1 + (i * 4);
    }
*/
    printf("%s\n", userId[0]);
    for(i=0; i<numOfReducers; i++){
        rdrNum = findRdrNum(userId[i]);
        strcpy(userToRdrMap[rdrNum], userId[i]);
        printf("%d", rdrNum);
    }
    return 0;
}

谢谢, 哈里什

【问题讨论】:

    标签: c arrays string pointers


    【解决方案1】:

    问题:

    您的问题有点令人困惑,但我可以看到您的意思,很明显您确实付出了一些努力来写出一篇像样的帖子。因此,我尝试编写一个程序来完成您想要的,并且易于理解。


    计划:

    以下程序会自动安装您在示例中提供的 same 示例字符串。然后它读取 main 的参数向量 (char *argc[]) 中提供的每个参数,以检查它是否在其中。如果不是,它将被安装。正如您将在示例输出中看到的那样,所有内容都通过 print 语句进行了演示。

    输出示例:

    ./a.out 2222 3223 kjd 090 kjd
    The User ID "2222" exists at index 1!
    The User ID "3223" exists at index 2!
    Installed "kjd"!
    Installed "090"!
    The User ID "kjd" exists at index 4!
    

    这是程序代码。我试图评论一些事情,所以你会明白我做了什么。在底部,我将包含一些关于您的原始程序的评论以及您可以做些什么来尝试和改进它。

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    
    // An array of strings: (A pointer to an array of pointers).
    // Declaring a variable out in global scope is always initialized to NULL!
    char **strings;
    
    // 1: The index of the next free pointer. 2: The total number of pointers available.
    int str_idx, str_size;
    
    // Returns index of given string pointer `sp`. If it's not there, -1 is returned.
    int indexOf (char *sp) {
    
        for (int i = 0; i < str_idx; i++) {
            // If string at index i == string we're looking for.
            if (strcmp(sp, strings[i]) == 0) {
                return i;
            }
        }
    
        return -1;
    }
    
    // Copies given string into strings. If necessary, strings is resized.
    void installString (char *sp) {
        char *copy = NULL;
    
        // 1. Ensure we have space. Otherwise resize strings array.
        if (str_idx >= str_size) {
            str_size = str_size * 2;
            strings = realloc(strings, str_size * sizeof(char *));
        }
    
        // 2. Allocate space for copy of string. Print an error if it failed.
        copy = malloc(strlen(sp) * sizeof(char));
        if (copy == NULL) {
            printf("Error: Allocation failure in \"installString\"!\n");
            exit(EXIT_FAILURE);
        }
    
        // 3. Copy the contents over.
        copy = strcpy(copy, sp);
    
        // 4. Place the pointer (copy) at str_idx (next available spot), and post-increment str_idx.
        strings[str_idx++] = copy;
    }
    
    int main (int argc, char *argv[]) {
    
        // 1. Initialize our global strings array. Ensure it succeeded.
        str_size = 10;
        strings = malloc(str_size * sizeof(char *));
        if (strings == NULL) {
            printf("Error: Allocation failure in \"main\"!\n");
            exit(EXIT_FAILURE);
        }
    
        // 2. Install some example User IDs.
        installString("2234");
        installString("2222");
        installString("3223");
        installString("2222");
    
        // 3. Check the User IDs provided as program arguments.
        // (I subtract from argc first because argument zero is the name of the program)
        while (--argc) {
            char *id = *(++argv); // Increment argv pointer (to skip program name), dereference at next argument.
    
            // 4. If the id is installed, print the index. Otherwise install it.
            int index;
            index = indexOf(id);
            if (index > -1) {
                printf("The User ID \"%s\" exists at index %d!\n", id, index);
            } else {
                installString(id);
                printf("Installed \"%s\"!\n", id);
            }
        }
    
        // 5. Clean up allocated memory.
        for (int i = 0; i < str_idx; i++) {
            free(strings[i]);
        }
        free(strings);
    
        return EXIT_SUCCESS;
    }
    

    提示:

    1. 如果您将其保留在 global 范围内,则声明:char** userToRdrMap = {NULL}; 最好写为 char **userToRdrMap;。原因是它将在那里自动初始化为 NULL。但是,如果它是局部变量或自动变量,则不是这样!

    2. 不要将零作为错误索引返回。零是一个有效的索引,而 -1 更有意义,因为索引 -1 处没有任何东西。

    3. 你的函数findRdrNum 说它返回一个int。然而最后你返回NULL,它通常用于指针。这是不一致的。不要这样做。

    4. 您没有释放您在程序中分配的任何内存。你真的应该这样做。

    5. 您在程序中分配了一个字符串数组指针。但是您实际上并没有为要复制到指针的字符串分配任何空间。这是未定义的行为并且是不可接受的。如果要将任何内容复制到分配的指针数组中,则需要为要复制的字符串实际分配内存,然后将字符串数组中的指针值设置为分配的副本的值。我在我的例子中这样做。

    【讨论】:

    • 只是为了推断。如果我要使用字符串创建一个类似二维矩阵的结构,我将如何为它们分配内存? char *** 是错误的。
    • 或者还有其他更好的数据结构可以产生相同的功能吗?请注意,我会在运行时获取行数。
    • 我喜欢你的例子,除了全局变量的使用。应该避免它们。相反,在main 中声明您需要的变量并根据需要传递一个引用。 (答案很努力)
    • @HarrishA 字符串是 char 类型的数组,因此 char *string 指的是单个字符串并且实际上是一维向量。字符串数组表示为指向字符串指针数组的指针。所以char **stringArray。这是一个二维结构。你是这个意思吗?如果是这样,那么示例代码将演示它。 3D 结构会将字符串分组为子数组。 char ***stringVectors 将是一个指向指针数组的指针,其中每个指针都指向它们自己的字符串指针数组。我从来没有使用过它。这有助于澄清一些事情吗?
    • @HarrishA 现在,如果您想要更好的功能,那么确实存在这样一种用于快速搜索字符串的结构。哈希表比字符串数组快得多,因为它可以在 O(1) 时间内找到字符串索引。通常,搜索一个字符串数组会让你在 O(n) 更糟的情况下找到一个包含“n”个元素的数组。不过,哈希表的工作量更大,如果您正在做一个简单的练习,那么我想迭代一个数组将足够快以满足您的需求。
    【解决方案2】:

    如果您尝试使函数findRdrNum 返回与userId 匹配的userToRdrMap 元素的索引,请考虑strcmp 如果其参数相等则返回0;因此,您的函数将重新运行不等于userId 的第一个元素的索引。检查strcmp(userToRdrMap[i], userId) == 0是否会很有用。此外,此函数的类型为int,因此没有return NULL 的位置。还有一点就是建议把malloc返回的结果给你的类型(malloc返回*void

    【讨论】:

      【解决方案3】:

      所以基本上你要找的是这样的东西?

      编辑:修复示例代码中的小错误

      int findRdrNum(char *userId) {
          for (int pos = 0; pos < numOfReducers; pos++) 
          // check whether position hasn't been allocated yet
          if (userToRdrMap[pos] != NULL) 
              //compare string and return pos if equal
              if (strcmp(userId, userToRdrMap[pos]) == 0)
                  return pos;
      }
      

      此外,您可以在这样的函数中分配向量:

      int allocateUserToRdrMap(){
          userToRdrMap = malloc(numOfReducers * sizeof(char));
          if (userToRdrMap == NULL)
              return 0;
          return 1;
      }
      

      【讨论】:

        【解决方案4】:

        您的问题有点不清楚您的最终目标是什么,但您似乎希望从 stdin(或文件)读取用户 ID,并将用户 ID 添加到动态扩展的指针数组中。执行此操作时,您似乎希望确保仅添加新 ID,并跳过所有重复的 ID。

        虽然使用指向 char 的指针数组(技术上是 pointer-to-pointer-to-char)没有任何问题,但如果您的所有 ID 都是固定数量的字符,您通过使用指向数组的指针(例如char (*users)[NCHR])作为存储的基础,可以大大简化您的内存分配。 (可以在一次调用中管理和释放存储,而不是分别为 ID 分配指针和存储)但是,因为您看起来想要一个 char ** 作为您的存储方案的基础(无论如何它都是您常用的东西) ),这将得到解决。

        当希望以动态方式存储任何内容时,使用指针到指针是这样做的正常基础。方案很简单。最初分配一些合理预期的指针数量,然后分配存储空间以保存每个指针指向的任何内容,当您用完初始指针数量时,只需 realloc 指针数量并继续。 (您可以在每次重新分配时添加任意数量的指针,通过将数量加倍是一种相当常见的方案)

        为了把所有这些放在一起,让我们从两个简单的函数开始。在用户集合中查找用户索引(如果存在)并返回索引(或者说-1,如果 ID 不存在)。你可以这样做:

        /* simple function to check if user exists & return index */
        int finduserndx (char **users, char *u, int n)
        {
            int i;
            
            for (i = 0; i < n; i++)             /* loop over each index */
                if (strcmp (users[i], u) == 0)  /* compare IDs as strings */
                    return i;                   /* return index on match */
            
            return -1;  /* return -1 indicating no-match */
        }
        

        您只需传递您的用户集合的副本、您希望添加的新用户和存在的号码,然后循环每个调用 strcmp 对新用户并返回它已经存在的索引或-1 表示它不存在于列表中。 (注意:如果您的用户 ID 都是数字,则将它们存储为十进制值会更有效)该函数将在您的 adduser 函数中使用。

        要添加新用户,请调用finduserndx,如果用户已经存在,则完成。如果不存在,则需要先检查是否已用完所有分配的指针,如果是,则reallocmore(总是将realloc的返回分配给临时指针),然后为新用户并将新用户复制到分配给列表中下一个可用指针的新内存块。你可以这样做:

        /* simple function to add user 'u' to 'users' with '*n' existing users,
         * and '*nptrs' allocated pointers in 'users'. reallocate if
         * '*n + 1 == *nptrs' and allocate for new `users[n]`, increment
         * '*n' and '*nptrs' accordingly. returns beginning address to `users`
         * which must be assigned in caller to prevent becoming a 3-star
         * programmer (not a compliment)
         */
        char **adduser (char **users, char *u, int *n, int *nptrs)
        {
            if (finduserndx (users, u, *n) != -1) { /* if users exists, return */
                printf ("user: %s ==> duplicate\n", u); /* informational only */
                return users;
            }
            printf ("user: %s --- adding\n", u);    /* informational only */
            
            if (*n + 1 == *nptrs) { /* check if num pointers exhausted */
                /* realloc using temporary pointer */
                void *tmp = realloc (users, *nptrs * 2 * sizeof *users);
                if (!tmp) { /* validate realloc success/handle failure */
                    perror ("realloc - memory exhausted");
                    exit (EXIT_FAILURE);
                }
                users = tmp;    /* assign new block to users */
                *nptrs *= 2;    /* update number of allocated pointers */
            }
            
            users[*n] = malloc (strlen (u) + 1);    /* allocate for new user */
            if (!users[*n]) {   /* vallidate malloc/handle failure */
                perror ("malloc users[n] - memory exhausted");
                exit (EXIT_FAILURE);
            }
            strcpy (users[(*n)++], u);  /* copy new user to users[n] & increment */
            
            return users;   /* return pointer to start of users */
        }
        

        (注意:如果realloc 失败,您现有的数据不会丢失并且仍然存在并且可以通过原始指针获得——因此您处理realloc 失败不需要立即成为exit——但这会增加一些复杂性,最好留到以后)

        (另请注意:当前用户 ID 的数量 ('n') 和当前分配的指针数量 ('nptrs') 如何作为 指针 传递当它们的值在函数中更新时——新值在调用函数中可用)

        这就是您的存储方案的基本要素。现在你需要在调用者中做的所有事情(main() 这里)是读取每个用户 ID,获取长度,修剪尾随 '\n'(所以 strcmp 工作 - 所以你没有虚假的 @987654342 @s 悬挂在字符串的末尾),然后调用 adduser 将字符串作为参数传递,如果是用户 ID,则传递指向当前数字的指针和指向当前分配指针数量的指针。

        总而言之,您可以执行以下操作:

        #include <stdio.h>
        #include <stdlib.h>
        #include <string.h>
        
        #define NREDUCR 4   /* if you need constants, define them */
        #define MAXC  128   /* (don't use magic numbers in code) */
        
        /* simple function to check if user exists & return index */
        int finduserndx (char **users, char *u, int n)
        {
            int i;
            
            for (i = 0; i < n; i++)             /* loop over each index */
                if (strcmp (users[i], u) == 0)  /* compare IDs as strings */
                    return i;                   /* return index on match */
            
            return -1;  /* return -1 indicating no-match */
        }
        
        /* simple function to add user 'u' to 'users' with '*n' existing users,
         * and '*nptrs' allocated pointers in 'users'. reallocate if
         * '*n + 1 == *nptrs' and allocate for new `users[n]`, increment
         * '*n' and '*nptrs' accordingly. returns beginning address to `users`
         * which must be assigned in caller to prevent becoming a 3-star
         * programmer (not a compliment)
         */
        char **adduser (char **users, char *u, int *n, int *nptrs)
        {
            if (finduserndx (users, u, *n) != -1) { /* if users exists, return */
                printf ("user: %s ==> duplicate\n", u); /* informational only */
                return users;
            }
            printf ("user: %s --- adding\n", u);    /* informational only */
            
            if (*n + 1 == *nptrs) { /* check if num pointers exhausted */
                /* realloc using temporary pointer */
                void *tmp = realloc (users, *nptrs * 2 * sizeof *users);
                if (!tmp) { /* validate realloc success/handle failure */
                    perror ("realloc - memory exhausted");
                    exit (EXIT_FAILURE);
                }
                users = tmp;    /* assign new block to users */
                *nptrs *= 2;    /* update number of allocated pointers */
            }
            
            users[*n] = malloc (strlen (u) + 1);    /* allocate for new user */
            if (!users[*n]) {   /* vallidate malloc/handle failure */
                perror ("malloc users[n] - memory exhausted");
                exit (EXIT_FAILURE);
            }
            strcpy (users[(*n)++], u);  /* copy new user to users[n] & increment */
            
            return users;   /* return pointer to start of users */
        }
        
        int main (int argc, char **argv) {
            
            /* allocate number of pointer given as argument (NREDUCR by default) */
            int nptrs = argc > 1 ? (int)strtol(argv[1], NULL, 0) : NREDUCR,
                ndx = 0, i;
            char user[MAXC] = "";   /* fixed buffer for reading input */
            char **users = NULL;    /* pointer to pointer to char */
            
            /* allocate/validate initial number of pointers */
            users = malloc (nptrs * sizeof *users);
            if (!users) {
                perror ("malloc - users");
                exit (EXIT_FAILURE);
            }
            
            while (fgets (user, MAXC, stdin)) {     /* read each ID from stdin */
                size_t len = strlen (user);         /* get length of ID */
                if (len && user[len - 1] == '\n')   /* check last char is '\n' */
                user[--len] = 0;                    /* overwrite w/nul-character */
                else if (len - 1 == MAXC) {         /* test if ID too long */
                    fprintf (stderr, "error: ID too long.\n");
                    exit (EXIT_FAILURE);
                }
                /* call adduser assigning return to users */
                users = adduser (users, user, &ndx, &nptrs);
            }
            
            putchar ('\n');         /* output stored user IDs */
            for (i = 0; i < ndx; i++) {
                printf ("user[%2d] : %s\n", i, users[i]);
                free (users[i]);    /* freeing storage as you go */
            }
            free (users);           /* free pointers */
            
            return 0;
        }
        

        输入 ID 示例(有重复)

        $ cat dat/userids.txt
        1993
        2947
        2234
        2222
        3223
        1016
        1444
        1125
        1194
        2234
        2732
        2679
        2222
        2681
        1444
        1629
        

        使用/输出示例

        $ ./bin/addusers_dyn <dat/userids.txt
        user: 1993 --- adding
        user: 2947 --- adding
        user: 2234 --- adding
        user: 2222 --- adding
        user: 3223 --- adding
        user: 1016 --- adding
        user: 1444 --- adding
        user: 1125 --- adding
        user: 1194 --- adding
        user: 2234 ==> duplicate
        user: 2732 --- adding
        user: 2679 --- adding
        user: 2222 ==> duplicate
        user: 2681 --- adding
        user: 1444 ==> duplicate
        user: 1629 --- adding
        
        user[ 0] : 1993
        user[ 1] : 2947
        user[ 2] : 2234
        user[ 3] : 2222
        user[ 4] : 3223
        user[ 5] : 1016
        user[ 6] : 1444
        user[ 7] : 1125
        user[ 8] : 1194
        user[ 9] : 2732
        user[10] : 2679
        user[11] : 2681
        user[12] : 1629
        

        内存使用/错误检查

        在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有 2 个职责:(1)始终保留指向起始地址的指针内存块,因此,(2) 当不再需要它时可以释放

        您必须使用内存错误检查程序来确保您不会尝试访问内存或写入超出/超出分配块的边界,尝试读取或基于未初始化的值进行条件跳转,最后, 以确认您已释放所有已分配的内存。

        对于 Linux,valgrind 是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。

        $ valgrind ./bin/addusers_dyn <dat/userids.txt
        ==26867== Memcheck, a memory error detector
        ==26867== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
        ==26867== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
        ==26867== Command: ./bin/addusers_dyn
        ==26867==
        user: 1993 --- adding
        user: 2947 --- adding
        user: 2234 --- adding
        user: 2222 --- adding
        user: 3223 --- adding
        user: 1016 --- adding
        user: 1444 --- adding
        user: 1125 --- adding
        user: 1194 --- adding
        user: 2234 ==> duplicate
        user: 2732 --- adding
        user: 2679 --- adding
        user: 2222 ==> duplicate
        user: 2681 --- adding
        user: 1444 ==> duplicate
        user: 1629 --- adding
        
        user[ 0] : 1993
        user[ 1] : 2947
        user[ 2] : 2234
        user[ 3] : 2222
        user[ 4] : 3223
        user[ 5] : 1016
        user[ 6] : 1444
        user[ 7] : 1125
        user[ 8] : 1194
        user[ 9] : 2732
        user[10] : 2679
        user[11] : 2681
        user[12] : 1629
        ==26867==
        ==26867== HEAP SUMMARY:
        ==26867==     in use at exit: 0 bytes in 0 blocks
        ==26867==   total heap usage: 16 allocs, 16 frees, 289 bytes allocated
        ==26867==
        ==26867== All heap blocks were freed -- no leaks are possible
        ==26867==
        ==26867== For counts of detected and suppressed errors, rerun with: -v
        ==26867== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
        

        始终确认您已释放已分配的所有内存并且没有内存错误。

        如果您还有其他问题或我错过了您的问题意图,请仔细查看并告诉我。如果您能说明您还需要什么,我很乐意提供进一步帮助。

        【讨论】:

          猜你喜欢
          • 2013-12-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-05-21
          相关资源
          最近更新 更多