Struct realloc中的动态字符串数组

时间:2016-04-19 18:29:36

标签: c arrays struct malloc realloc

我在Struct里面有一个字符串数组,看起来像这个

#define ID_LEN 20

struct Person{
char name[ID_LEN];
int num_items;
char **items;
};

int main(){
int num_persons = 10;
struct Person *ptr[num_persons];

我从一群10人开始。最初所有人都有0个项目,因此他们的项目列表是malloc(0)。

for(int i = 0; i < num_persons; i++){
        ptr[i] = (struct Person *) malloc(sizeof(struct Person) + 1);
        ptr[i]->num_items = 0;
        ptr[i]->items = malloc(0 * sizeof(char*));
}

在某些时候,我想为这些人命名并添加一些这样的项目。

strcpy(ptr[0]->name, "John");
ptr[0]->num_items = 1;
ptr[0]->items = (char **)realloc(ptr[0]->items, ptr[0]->num_items * sizeof(char*));
strcpy(ptr[0]->items[0], "pencil");


printf("Name: %s\n", ptr[0]->name);
printf("Number of items: %d\n", ptr[0]->num_items);
for(int i = 0; i < ptr[0]->num_items; i++){
    printf("Item %d is %s\n", i, ptr[0]->items[i]);
}

我得到了分段错误:11。我不确定我是否正确地进行了realloc。

4 个答案:

答案 0 :(得分:2)

这里有几个问题

首先是ID_LEN

您无处可查看是否通过复制到名称超过ID_LEN

所以而不是

strcpy(ptr[0]->name, "John"); 

使用

strcpy_s(ptr[0]->name,sizeof(ptr[0]->name),"John or whatever"); // C11

您分配

ptr[0]->items = (char **)realloc(ptr[0]->items, ptr[0]->num_items * sizeof(char*))

但是

你不应该转换malloc/realloc的返回值(在网上搜索) 解释,它已被描述为广告)

使用realloc时,首先应检查返回值,否则可能会失败。

char** tmp = realloc(ptr[0]->items, ptr[0]->num_items * sizeof(char*))
if (tmp != NULL)
{
  ptr[0]->items = tmp;
}
else
{
  abort();
}

内存realloc返回部分未初始化(旧指针仍然存在但是 新的未初始化。在你的情况下你没有任何以前 指针,以便一个items[0]未初始化。

所以当你这样做时

strcpy(ptr[0]->items[0], "pencil");

它将失败,因为items[0]指向某个任意内存位置。

在你realloc指针后,你需要初始化它们指向a 内存足够大以容纳你的字符串

E.g。

ptr[0]->items[0] = strdup("pencil");  // does malloc then copies string

每次需要添加一个新的时候使用realloc效率不高 item,而是分配一堆项目,但设置你不使用的项目 为NULL然后跟踪剩余的数量,一旦他们用完了allocate 另一堆

答案 1 :(得分:1)

代码中的所有问题都围绕着使用未分配的内存。 考虑

 ptr[0]->items = (char **)realloc(ptr[0]->items, ptr[0]->num_items * sizeof(char*));//line 1
 strcpy(ptr[0]->items[0], "pencil");//line 2

在第1行中,你已经分配了内存以保存{c}个字符串的ptr[0]->num_items个指针。但是你实际上没有分配内存来存储c字符串,即那些指针没有指向实际的内存。 在第2行中,您尝试访问ptr[0]->items[0]char*,没有分配内存。 我在第2行之前添加了以下行,您的代码工作正常。

 for(int i=0;i<ptr[0]->num_items;i++)
      ptr[0]->items[i]=malloc(sizeof(char)*10);

答案 2 :(得分:0)

您正在分配项目列表,但您没有为每个单独的项目字符串分配空间。你的

strcpy(ptr[0]->items[0], "pencil");

会失败。

答案 3 :(得分:0)

除了其他答案提供的内容之外,还有其他一些与您正在做的事情相关的考虑因素。第一个,不要0分配任何东西。这是过去几天的延续,而不是必要的。

接下来,在静态声明可变长度数组num_persons指向类型struct Person的数组之后,您必须决定是否一次为所有指针分配存储空间,或者是否要分配存储空间对于结构,仅在添加人员时。当您将代码拆分为函数时,这将产生影响。

由于数据结构的其余部分将动态分配,因此您必须在复制/初始化之前验证每个分配是否成功,无论如何。

分配空结构时,请仔细查看calloc而不是malloc。这是calloc提供的默认初始化可以提供帮助的一个实例。

你的任务的其余部分只是一个会计问题 - 它考虑了哪些成员需要分配,每个分配的当前状态和大小,然后最终释放你不再需要时分配的内存。

以您可能想要的代码为例,将添加到指针数组中。要验证添加,您需要知道ptr是一个有效的指针,以及在开始分配内存之前是否已达到num_persons限制。接下来,您必须为struct Person分配存储空间,并将该块的地址分配给ptx[X](对于您正在使用的索引X)。

分配后,您可以初始化默认值(或者如果使用calloc,则知道所有值都已初始化为0/NULL)。接下来,您可以复制name(限于ID_LEN)并为您希望在指针中保存的每个分配指针存储空间-pointer-to-char * items(您将处理与ptr类似的接受,同时也会分配items的指针)注意,不需要realloc (items,...此时,这是第一次分配items

如果你将所有这些放在一起,你可以提出一个功能来添加一个和他们的第一个,类似于以下内容,{{1是允许的的最大数量,max是名称,nm是项目,itm是指向当前索引的指针人:

idx

(注意:你必须在调用函数中将函数的返回值分配给/* add person with item */ struct person *addpwitem (struct person **ptr, int max, char *nm, char *itm, int *idx) { if (!ptr || *idx + 1 == max) return NULL; int i = *idx; /* allocate storage for struct */ if (!(ptr[i] = calloc (1, sizeof **ptr))) { fprintf (stderr, "error: ptr[%d], virtual memory exhausted.\n", i); return NULL; } strncpy (ptr[i]->name, nm, ID_LEN); /* copy name */ /* allocate/validate pointer to char* ptr[i]->items */ if (!(ptr[i]->items = malloc (ptr[i]->num_items + 1 * sizeof *(ptr[i]->items)))) { fprintf (stderr, "error: items*, virtual memory exhausted.\n"); return NULL; } /* allocate/validate memory for ptr[num_items]->items[num_items] */ if (!(ptr[i]->items[ptr[i]->num_items] = strdup (itm))) { fprintf (stderr, "error: items, virtual memory exhausted.\n"); return NULL; } ptr[i]->num_items++; (*idx)++; return ptr[i]; } 。另请注意,C风格推荐所有小写名称,这就是你在上面看到的,留下 CamelCase 到C ++的名称)

添加人员后,您需要能够将项目添加到与该人员相关联的项目列表中。这是ptr[X]允许您调整该人的指针数量的地方。您可以执行类似添加人员的操作,但您传递的索引不再需要是指针,因为它不会在函数中更新。 e.g。

realloc

您现在可以将添加到列表中,但是如果要使用,则需要一种方法来迭代列表中的所有值他们无论是搜索,打印还是释放内存,所有迭代函数都具有相似的形式。要打印所有,您可以执行类似以下操作:

/* add item to person at index 'i' */
struct person *additem (struct person **ptr, int i, char *itm)
{
    if (!ptr) return NULL;
    void *tmp;

    /* allocate/realloc/validate pointer to char* ptr[i]->items  */
    if (!(tmp = realloc (ptr[i]->items,
                        (ptr[i]->num_items + 1) * sizeof *(ptr[i]->items)))) {
        fprintf (stderr, "error: items*, virtual memory exhausted.\n");
        return NULL;
    }
    ptr[i]->items = tmp;    /* assign tmp on successful realloc */

    /* allocate/validate memory for ptr[num_items]->items[num_items] */
    if (!(ptr[i]->items[ptr[i]->num_items] = strdup (itm))) {
        fprintf (stderr, "error: items, virtual memory exhausted.\n");
        return NULL;
    }
    ptr[i]->num_items++;

    return ptr[i];
}

在你的动态分配内存的任何代码中,你有2个责任关于任何分配的内存块:(1)总是保留一个指向内存块起始地址的指针,所以,(2)它可以在释放时释放它不再需要了。完成列表后,您需要/* print the list of persons and items */ void prndetail (struct person **ptr, int idx) { if (!ptr || !*ptr) return; int i, p; for (p = 0; p < idx; p++) { printf (" %-20s %2d", ptr[p]->name, ptr[p]->num_items); for (i = 0; i < ptr[p]->num_items; i++) printf ("%s%s", i ? ", " : " ", ptr[p]->items[i]); putchar ('\n'); } } 它。与打印类似,可以按如下方式完成:

free

这真的是会计课程的全部内容。如果您跟踪正在使用的数据结构的每个部分,那么管理内存是直截了当的。在一个简短的例子中将所有部分组合在一起,您可以执行以下操作:

/* free all allocated memory */
void deletelist (struct person **ptr, int idx)
{
    if (!ptr || !*ptr) return;
    int i, p;
    for (p = 0; p < idx; p++) {
        for (i = 0; i < ptr[p]->num_items; i++)
            free (ptr[p]->items[i]);
        free (ptr[p]->items);
        free (ptr[p]);
    }
    // free (ptr);  /* if array of pointers allocated, not static */
}

示例使用/输出

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

enum { NPER = 10, ID_LEN = 20 };

struct person {
    char name[ID_LEN];
    int num_items;
    char **items;
};

/* add person + item - to add both person and item at once */
struct person *addpwitem (struct person **ptr, int max, char *nm,
                            char *itm, int *idx);
/* add item to existing person */
struct person *additem (struct person **ptr, int i, char *itm);
void prndetail (struct person **ptr, int idx);
void deletelist (struct person **ptr, int idx);

int main (void) {

    int idx = 0;
    struct person *ptr[NPER];

    /* allocate storage for struct, add person + item */
    if (!(ptr[idx] = addpwitem (ptr, NPER, "John", "pencils", &idx))) {
        fprintf (stderr, "error: adding ptr[%d] failed.\n", idx);
        return 1;
    }

    printf ("\nadded John:\n");
    prndetail (ptr, idx);       /* print contents of persons & items */

    additem (ptr, idx - 1, "pens"); /* add next item */
    printf ("\nadded item 'pens' for John:\n");
    prndetail (ptr, idx);       /* print contents of persons & items */

    /* add next person + item */
    if (!(ptr[idx] = addpwitem (ptr, NPER, "Martha", "paper", &idx))) {
        fprintf (stderr, "error: adding ptr[%d] failed.\n", idx);
        return 1;
    }

    printf ("\nadded Martha:\n");
    prndetail (ptr, idx);       /* print contents of persons & items */

    deletelist (ptr, idx);      /* free all allocated memory */

    return 0;
}

/* add person with item */
struct person *addpwitem (struct person **ptr, int max, char *nm,
                            char *itm, int *idx)
{
    if (!ptr || *idx + 1 == max) return NULL;
    int i = *idx;

    /* allocate storage for struct */
    if (!(ptr[i] = calloc (1, sizeof **ptr))) {
        fprintf (stderr, "error: ptr[%d], virtual memory exhausted.\n", i);
        return NULL;
    }
    strncpy (ptr[i]->name, nm, ID_LEN);  /* copy name */

    /* allocate/validate pointer to char* ptr[i]->items  */
    if (!(ptr[i]->items = 
        malloc (ptr[i]->num_items + 1 * sizeof *(ptr[i]->items)))) {
        fprintf (stderr, "error: items*, virtual memory exhausted.\n");
        return NULL;
    }
    /* allocate/validate memory for ptr[num_items]->items[num_items] */
    if (!(ptr[i]->items[ptr[i]->num_items] = strdup (itm))) {
        fprintf (stderr, "error: items, virtual memory exhausted.\n");
        return NULL;
    }
    ptr[i]->num_items++;
    (*idx)++;

    return ptr[i];
}

/* add item to person at index 'i' */
struct person *additem (struct person **ptr, int i, char *itm)
{
    if (!ptr) return NULL;
    void *tmp;

    /* allocate/realloc/validate pointer to char* ptr[i]->items  */
    if (!(tmp = realloc (ptr[i]->items,
                        (ptr[i]->num_items + 1) * sizeof *(ptr[i]->items)))) {
        fprintf (stderr, "error: items*, virtual memory exhausted.\n");
        return NULL;
    }
    ptr[i]->items = tmp;    /* assign tmp on successful realloc */

    /* allocate/validate memory for ptr[num_items]->items[num_items] */
    if (!(ptr[i]->items[ptr[i]->num_items] = strdup (itm))) {
        fprintf (stderr, "error: items, virtual memory exhausted.\n");
        return NULL;
    }
    ptr[i]->num_items++;

    return ptr[i];
}

/* print the list of persons and items */
void prndetail (struct person **ptr, int idx)
{
    if (!ptr || !*ptr) return;
    int i, p;
    for (p = 0; p < idx; p++) {
        printf (" %-20s   %2d", ptr[p]->name, ptr[p]->num_items);
        for (i = 0; i < ptr[p]->num_items; i++)
            printf ("%s%s", i ? ",  " : " ", ptr[p]->items[i]);
        putchar ('\n');
    }
}

/* free all allocated memory */
void deletelist (struct person **ptr, int idx)
{
    if (!ptr || !*ptr) return;
    int i, p;
    for (p = 0; p < idx; p++) {
        for (i = 0; i < ptr[p]->num_items; i++)
            free (ptr[p]->items[i]);
        free (ptr[p]->items);
        free (ptr[p]);
    }
    // free (ptr);  /* if array of pointers allocated */
}

内存错误检查

必须使用内存错误检查程序,以确保您没有在已分配的内存块之外/之外写入,尝试读取或基于未初始化的值跳转,最后确认您已拥有释放了你分配的所有内存。

这很简单:

$ ./bin/struct_p2p2c

added John:
 John                    1 pencils

added item 'pens' for John:
 John                    2 pencils,  pens

added Martha:
 John                    2 pencils,  pens
 Martha                  1 paper

始终确认释放所有堆块 - 不可能泄漏并且同样重要错误摘要:0个上下文中的0个错误

仔细研究这个以及其余的答案。他们之间有很多好的建议。如果您还有其他问题,请告诉我们。