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

ALL*_*LAN 0 c dynamic-arrays

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

Dav*_*ica 5

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

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

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

block = realloc (block, 2 * n * sizeof *block);    /* to double size */
Run Code Online (Sandbox Code Playgroud)

如果realloc失败——返回什么,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 */
Run Code Online (Sandbox Code Playgroud)

注意:使用void *tmp是因为这是 的返回类型realloc。它可能只是有效地int *tmp匹配 的类型block。关键是您的临时指针必须与重新分配块的分配tmp类型兼容,例如。对于新的 C 程序员,在 C 中,任何指针都可以在没有显式转换的情况下进行转换。因此,它只是一个通用的临时类型,但 for 的类型在那里是正确的)blockblock = tmp;void*void *tmpblock

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

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

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

您重新分配并将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 */
}
Run Code Online (Sandbox Code Playgroud)

使用/输出示例

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

$ ./bin/malloc_realloc < ../dat/100000int.txt
no. of bytes allocated : 524288
no. of bytes used      : 400000
no. of ints stored     : 100000
Run Code Online (Sandbox Code Playgroud)

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

void *tmp = realloc (block, used * sizeof *block);
...
Run Code Online (Sandbox Code Playgroud)

(这留给你去试验——但你不能realloc让大小小于存储值的数量)

内存使用/错误检查

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

您必须使用内存错误检查程序来确保您不会尝试访问内存或在分配的块的范围之外进行写入,尝试读取未初始化的值或将条件跳转建立在未初始化的值上,最后确认释放所有已分配的内存。

对于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)
Run Code Online (Sandbox Code Playgroud)

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

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

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

如果您想检查总分配总计,您可以使用循环for和 POSIX 数学运算符从 shell(假设是 POSIX shell)执行此操作,例如

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