如果 malloc() 只分配一个只能存储一个变量的内存块,那么如何使用 malloc() 来创建动态数组?

how malloc() can be used to create a dynamic array if it allocates only one block of memory which can be stored for only one variable?

malloc()函数创建一整块内存然后return第一个字节的指针。如果它只创建一个内存块,那么它只能存储一个元素,对吗?但是 malloc() 如何将多个元素仅存储在一个内存块中,而当 calloc() 创建多个内存块并且它可以用作动态数组时才有意义。

是的,它需要 only one block,但根据您要求的所需大小,这一块也可以分成无限块。

按照我的理解,malloc()分配一个内存,但保证是连续的。数组只是指向连续内存第一个元素的指针(由程序员来跟踪数组的大小)。请注意,这与 malloc() 的输出相同。一个 内存可以包含任意数量的元素,只要您为所需数量的元素创建了足够大的块即可。 calloc() 做完全相同的事情,但将块初始化为全零,这样初始化数组就更方便了。

这是基本的分配内存管理。无论您 allocating/reallocating 是什么类型的数据,它的工作方式都是一样的。为动态内存分配提供的三个标准函数是malloc/calloc/realloc。虽然 malloc/calloc 仅限于分配单个内存块,但 realloc 也可以 调整内存块的大小 允许您分配一些初始存储块 malloc/calloc(或者你可以用realloc)然后用realloc增加或减少分配的内存量。

与分配内存的每个函数调用一样,您必须通过检查 return 不是 NULL 来验证调用是否成功。这就是为什么在使用 realloc 时,您 总是 realloc 使用临时指针!。因此,当(不是如果)realloc 由于 运行 内存不足而失败时,您不会用 NULL 覆盖原始指针,从而丢失内存块的地址并造成内存泄漏.

例如,如果您已分配给 int *block = malloc (...),则您不会 realloc 为:

block = realloc (block, 2 * n * sizeof *block);    /* to double size */

如果 realloc 失败了——return 是什么,block 现在是什么? (提示:NULL)。因此,您可以使用临时指针,例如

/* always realloc to a temp pointer, or risk losing pointer */
void *tmp = realloc (block, 2 * n * sizeof *block);   /* doubling size */
if (!tmp) {         /* validate every allocation */
    perror ("realloc-block");
    /* handle error - original pointer still good */
}
block = tmp;        /* assign reallocated block */

(注意: void *tmp 的使用已经完成,因为这是 realloc 的 return 类型。它可能只是一个有效的int *tmp 以匹配 block 的类型。关键是你的临时指针 tmp 必须与 block 类型兼容才能分配重新分配的块,例如 block = tmp;。对于新的 C 程序员,在 C 中,任何指针都可以转换为 to/from void* 而无需显式转换。因此 void *tmp 只是一个通用的临时类型,但 to/from void* 的类型=32=] 那里的每一点都是正确的)

这样,在 验证 realloc 的调用成功之前,您不会分配重新分配的块。你可以增加任何你喜欢的数字。加倍也可以,或者只是加一些固定的数字也可以,等等。

处理任何重新分配的方案很简单。您分配一个块来保存一定数量的对象。您保留分配的数字的计数器(比如 alloced)。然后你保留一个单独的计数器来记录所使用的对象数量(比如 used)。然后在将内存用于另一个对象之前,如果填充循环等,您只需检查是否 used == alloced,例如:

if (used == alloced) {          /* check if realloc needed */
    /* realloc here */
    alloced *= 2;               /* update no. allocated (doubling size here) */
}

您重新分配 alloced 计数器并将其更新为新大小,然后继续...

一个简短示例,最初分配 2 个整数,然后根据需要通过将当前分配大小加倍来读取和存储以及无限数量的整数来重新分配,这是一个基本示例。如上所述,您可以根据需要增加分配大小,但要避免每次添加都重新分配。内存分配是一个相对昂贵的调用。所以加倍,例如将分配从 2 的存储增加到 4, 8, 16, 32, 64, 128, ... 的存储是一个合理的增长率,可以避免在添加对象时重复不必要的重新分配。

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

#define MAXI 2                  /* initial number of integer to allocate */

int main (void) {

    size_t  alloced = MAXI,     /* counter to track number allocate */
            used = 0;           /* counter to track number used */
    int val = 0,                /* value to read from stdin */
        *block = malloc (alloced * sizeof *block);  /* initial allocation */

    if (!block) {   /* validate every allocation */
        perror ("malloc-block");
        return 1;
    }

    while (scanf ("%d", &val) == 1) {   /* while integer read */
        if (used == alloced) {          /* check if realloc needed */
            /* always realloc to a temp pointer, or risk losing pointer */
            void *tmp = realloc (block, 2 * alloced * sizeof *block);
            if (!tmp) {         /* validate every allocation */
                perror ("realloc-block");
                if (!used)      /* if no ints stored, exit */
                    return 1;
                break;          /* otherwise original pointer still good */
            }
            block = tmp;        /* assign reallocated block */
            alloced *= 2;       /* update no. of ints allocated */
        }
        block[used++] = val;    /* add value to block and update used */
    }

    printf ("no. of bytes allocated : %zu\n"
            "no. of bytes used      : %zu\n"
            "no. of ints stored     : %zu\n",
            alloced * sizeof *block, used * sizeof *block , used);

    free (block);   /* don't forget to free what you allocate */
}

示例Use/Output

我们最初分配给 2-int,所以让我们尝试阅读 100000 看看进展如何...

$ ./bin/malloc_realloc < ../dat/100000int.txt
no. of bytes allocated : 524288
no. of bytes used      : 400000
no. of ints stored     : 100000

因此您可以看到我们在最后 realloc 上过度分配了 124288-bytes。不错,但由于您知道使用的整数数量,您可以 realloc 最后一次调整块大小以准确容纳 100000-int 通过:

void *tmp = realloc (block, used * sizeof *block);
...

(留给您进行试验 -- 但您不能 realloc 的大小小于存储值的数量)

内存Use/Error检查

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

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

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

$ valgrind ./bin/malloc_realloc < ../dat/100000int.txt
==28169== Memcheck, a memory error detector
==28169== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==28169== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==28169== Command: ./bin/malloc_realloc
==28169==
no. of bytes allocated : 524288
no. of bytes used      : 400000
no. of ints stored     : 100000
==28169==
==28169== HEAP SUMMARY:
==28169==     in use at exit: 0 bytes in 0 blocks
==28169==   total heap usage: 17 allocs, 17 frees, 1,048,568 bytes allocated
==28169==
==28169== All heap blocks were freed -- no leaks are possible
==28169==
==28169== For counts of detected and suppressed errors, rerun with: -v
==28169== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

(注:上面分配的1,048,568字节是2 * sizeof(int), 4 * sizeof(int), ....的总和)

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

检查一下,如果您还有其他问题,请告诉我。

如果你想检查总分配加起来,你可以从你的shell(假设POSIX shell)用一个for循环和POSIX 数学运算符,例如

sum=0; for i in $(seq 1 17); do sum=$((sum + (1 << i) * 4)); done; echo $sum
1048568