如何将项目动态添加到动态数组

How to add items dynamically to dynamic array

我正在尝试使用函数将项目添加到动态数组。我正在做的是计算数组的大小并将其增加 1。我的问题是计算数组的大小; sizeof(*tab) / sizeof((*tab)[0]) 始终为零。

struct table
{
    char key[20];
    int val;
};

struct table *symTab, *insTab, *datTab;

void addFieldToTable(struct table **tab, char key[MAX_KEY_LENGTH], int val) {
    struct table temp;
    strcpy(temp.key, key);
    temp.val = val;

    size_t space;
    if (*tab == 0) {
        space = 1;
        *tab = malloc(space);
        *tab[0] = temp;
        return;
    }
    //#region for test only
    size_t sizeOfTab = sizeof(*tab);//4
    size_t sizeOfEntity1 = sizeof((*tab)[0]);//24
    size_t sizeOfEntity2 = sizeof(**tab);//24
    size_t sizeOfEntity3 = sizeof(struct table);//24
    //#endregion
    space = sizeof(*tab) / sizeof((*tab)[0]);//always gets zero
    space++;
    *tab = realloc(*tab, space);
    *tab[space - 1] = temp;

}

int main()
{
    addFieldToTable(&insTab, "mov", 0);
    addFieldToTable(&insTab, "cmp", 1);
}

在我的代码中,第一项已成功添加,但当尝试添加第二项时计算的大小为零,因此数组不会调整大小,因此第一项 *tab[0] 被替换通过新项目。

如何解决这个计算数组大小的问题,使函数能够动态添加项目?。

您正在为具有 24B + 填充的结构分配 1B。

您要做的是为 sizeof 您的 struct.

分配内存
*tab = malloc(sizeof(struct table));

sizeof

sizeof(*tab); // Is just size of pointer

sizeof(**tab); === sizeof(struct table);  // It's size of Structure

如果您想知道数组有多少个元素,请传递另一个包含该信息的参数。

void addFieldToTable(struct table **tab, char key[MAX_KEY_LENGTH], int val, int noElements)

int noElements = 0;
addFieldToTable(&insTab, "mov", 0, noElements++);
addFieldToTable(&insTab, "cmp", 1, noElements++);
addFieldToTable(&insTab, "third", 1, noElements++);

重新分配是一项昂贵的操作,例如,您应该为 100 个结构分配 space,然后,如果您需要更多 space,则为 200 个结构重新分配。

如果你搞不懂,你的代码有更正

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

#define MAX_KEY_LENGTH 20
struct table
{
    char key[MAX_KEY_LENGTH];
    int val;
};

struct table *symTab, *insTab, *datTab;

void addFieldToTable(struct table **tab, char key[MAX_KEY_LENGTH], int val, int noElements) {
    struct table temp;
    strcpy_s(temp.key, key);
    temp.val = val;

    if (*tab == NULL)
    {
        *tab = malloc(sizeof(struct table));
        (*tab)[0] = temp;
        return;
    }

    *tab = realloc(*tab, ((noElements + 1) * sizeof(struct table)));

    (*tab)[noElements] = temp;
}

int main()
{
    int noElements = 0;
    addFieldToTable(&insTab, "mov", 0, noElements++);
    addFieldToTable(&insTab, "cmp", 1, noElements++);
    addFieldToTable(&insTab, "third", 1, noElements++);

    for (int i = 0; i < noElements; i++)
    {
        printf("\n%d: %s, %d", i+1, insTab[i].key, insTab[i].val);
    }

    return 0;
}

输出

1: mov, 0
2: cmp, 1
3: third, 1

您可能让事情变得比他们需要的更难。对于初学者,您始终可以减少跟踪当前分配的 table 结构数量所需的工作,方法是在 data 周围添加一个包含计数器的简单包装器。您也可以在这里使用 typedef 来发挥您的优势。

例如,与其直接处理 table,不如将 table 的元素移动到它自己的一个简单结构中,比如 tbl_data 并简单地在 table 到数据和分配的 tbl_data 元素数的计数器?它可以像这样工作:

typedef struct {
    char key[MAX_KEY_LENGTH];
    int val;
} tbl_data;

typedef struct {    /* simple wrapper preserving allocation size in ntbl */
    tbl_data *tbl;
    size_t ntbl;
} table;

接下来,避免使用全局变量。您已经将 **tab 作为参数传递,无需将 insTab 声明为全局参数。在 main() 中声明它作为参数传递,就像您打算做的那样。 (注意: 使用包装器,您可以使用 table 的简单静态实例并将其作为 *tab 传递)

想想上面的结构方案。您只为添加的每个 tbl_data 分配,并且在 分配 验证 新内存之后,您只需分配 temptab->tbl[tab->ntbl] 并将 tbl_data (tab->ntbl) 的数量增加 1。简单得多。由于每个 addFieldToTable 都增加了 1 的分配,因此您可以只对第一个和所有后续分配使用 realloc 来简化 addFieldToTable

(注意: 为每个添加的元素重复调用 malloc(或 realloccalloc)并不是一个非常有效的分配方案, 但暂时没问题。你可以在 tbl->ntbl 达到限制时分配 32512realloc 的块,或者每次都加倍 tbl->ntblrealloc 当您达到新限制以减少 realloc 调用时)

如果您在 main() 中声明 table insTab = { NULL, .ntbl = 0 };,您的 addFieldToTable 经过改进的验证可以减少为:

void addFieldToTable (table *tab, char *key, int val) 
{
    if (!key) {     /* validate key is not NULL */
        fprintf (stderr, "error: key parameter is NULL\n");
        return;
    }

    tbl_data temp;
    size_t keylen = strlen (key);   /* get the length of key */

    if (keylen > MAX_KEY_LENGTH) {  /* truncate if > MAX_KEY_LENGTH */
        key[keylen - 1] = 0;
        fprintf (stderr, "warning: key exceeds %d, truncated.\n", 
                MAX_KEY_LENGTH);
    }

    strcpy (temp.key, key);
    temp.val = val;

    /* just realloc tbl_data, since you are increasing by 1 each time */
    void *tmptbl = realloc (tab->tbl, (tab->ntbl + 1) * sizeof *(tab->tbl));

    if (!tmptbl) {  /* validate realloc succeeded or handle error */
        fprintf (stderr, "error: memory exhausted at '%zu' elements.\n",
                tab->ntbl);
        return;
    }
    tab->tbl = tmptbl;              /* assign new block to *tab */

    tab->tbl[tab->ntbl] = temp;     /* assign temp    */
    tab->ntbl++;                    /* increment ntbl */
}

(注意: 你真的应该将函数类型更改为 tbl_data* 和 return 指向 tab->tbl 的指针,这样你就可以 (1 ) return success/failure 的指示,例如指针或 NULL;以及 (2) 使指向新的 tbl_data 的指针立即可用。您也可以将其声明为输入 int 和 return 1 表示成功,输入 0 表示失败并保留上面 (1) 的好处)

总而言之,您可以执行类似于以下操作的操作:

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

#define MAX_KEY_LENGTH 20

typedef struct {
    char key[MAX_KEY_LENGTH];
    int val;
} tbl_data;

typedef struct {    /* simple wrapper preserving allocation size in ntbl */
    tbl_data *tbl;
    size_t ntbl;
} table;

void addFieldToTable (table *tab, char *key, int val) 
{
    if (!key) {     /* validate key is not NULL */
        fprintf (stderr, "error: key parameter is NULL\n");
        return;
    }

    tbl_data temp;
    size_t keylen = strlen (key);   /* get the length of key */

    if (keylen > MAX_KEY_LENGTH) {  /* truncate if > MAX_KEY_LENGTH */
        key[keylen - 1] = 0;
        fprintf (stderr, "warning: key exceeds %d, truncated.\n", 
                MAX_KEY_LENGTH);
    }

    strcpy (temp.key, key);
    temp.val = val;

    /* just realloc tbl_data, since you are increasing by 1 each time */
    void *tmptbl = realloc (tab->tbl, (tab->ntbl + 1) * sizeof *(tab->tbl));

    if (!tmptbl) {  /* validate realloc succeeded or handle error */
        fprintf (stderr, "error: memory exhausted at '%zu' elements.\n",
                tab->ntbl);
        return;
    }
    tab->tbl = tmptbl;              /* assign new block to *tab */

    tab->tbl[tab->ntbl] = temp;     /* assign temp    */
    tab->ntbl++;                    /* increment ntbl */
}

int main (void)
{
    table insTab = { NULL, .ntbl = 0 };

    addFieldToTable (&insTab, "mov", 0);
    addFieldToTable (&insTab, "cmp", 1);
    addFieldToTable (&insTab, "next command", 1234);

    if (insTab.ntbl == 0) {
        fprintf (stderr, "error: insTab is empty.\n");
        exit (EXIT_FAILURE);
    }

    for (size_t i = 0; i < insTab.ntbl; i++)
        printf ("%-*s : %d\n", MAX_KEY_LENGTH, 
                insTab.tbl[i].key, insTab.tbl[i].val);

    free (insTab.tbl);  /* don't forget to free allocated memory */

    return 0;
}

例子Use/Output

$ ./bin/struct_table
mov                  : 0
cmp                  : 1
next command         : 1234

内存Use/Error检查

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

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

对于Linux valgrind是正常的选择。每个平台都有类似的内存检查器。它们都很简单易用,只需运行你的程序就可以了。

$ valgrind ./bin/struct_table
==4003== Memcheck, a memory error detector
==4003== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==4003== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==4003== Command: ./bin/struct_table
==4003==
mov                  : 0
cmp                  : 1
next command         : 1234
==4003==
==4003== HEAP SUMMARY:
==4003==     in use at exit: 0 bytes in 0 blocks
==4003==   total heap usage: 3 allocs, 3 frees, 144 bytes allocated
==4003==
==4003== All heap blocks were freed -- no leaks are possible
==4003==
==4003== For counts of detected and suppressed errors, rerun with: -v
==4003== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

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

检查一下,如果您有任何问题,请告诉我。

这行space = sizeof(*tab) / sizeof((*tab)[0]);//always gets zero确实很烂!

tab被声明为一个struct table **,所以*tab是一个指针而sizeof(*tab)只是一个指针的大小...假设struct table包含多个单个元素,很可能是sizeof(struct table) > sizeof(struct table *),所以整数除法一般为0。

因此您必须自己保留分配的大小(您无法直接从 C 获取它)。但是无论如何,realloc 可能是一个昂贵的操作,所以你应该分块分配并保持分配的大小和使用的大小。