为什么malloc()不分配足够的内存

时间:2019-07-08 22:51:57

标签: c multidimensional-array malloc

我在理解DeclarativeContent的功能时遇到了一些麻烦,也许这就是为什么我遇到此问题,但是希望这里有人可以帮助我理解。

我正在使用以下函数分配2D数组:

malloc

我这样调用此函数:

int16_t** create_2d_array(uint8_t num_rows, uint16_t num_cols){
    uint8_t i = 0;  

    int16_t **arr = (int16_t **)malloc(num_rows * sizeof(int16_t *));

    for(i=0; i<num_rows; i++) {
        arr[i] = (int16_t *)malloc(num_cols * sizeof(int16_t));
    }               

    return arr;     
}

如果我在for循环的开始处停止代码,并使用GDB终端检查twoD = create_2d_array(4, 512); 的内容,则会得到以下信息:

arr

至少对我而言,这意味着gdb $ p/x arr[0] $96 = 0x2001fa00 gdb $ p/x arr[1] $97 = 0x0 gdb $ p/x arr[2] $98 = 0x0 gdb $ p/x arr[3] $99 = 0x0 的分配正确,但arr[0]的其他要素分配不正确。

不是arr应该确定请求的大小是否可以分配,如果不能分配,那么它应该返回malloc指针?至少那是我对NULL的理解。我在做错什么吗?

作为测试,我执行了以下行:

malloc

同样,我在for循环的开始处停止执行,并打印twoD_temp = create_2d_array(2, 4); 的内容。

arr

这是我所期望的。第一个索引是有效的指针,第二个索引也是有效的指针,因为我创建了一个指针数组。

在执行for循环后,我将打印相同的内容:

gdb $ p/x arr[0]
$121 = 0x2001fa00

gdb $ p/x arr[1]
$122 = 0x2001fa10

这仍然是我所期望的。现在唯一的区别是已为各列分配了内存。

4 个答案:

答案 0 :(得分:4)

仅在第一个malloc之后,arr是指向一块内存的指针,该内存除了垃圾之外什么都没有。 for循环将各个条目设置为指向行。

arr[0]循环将其值设置为指向其创建的各个行之前,arr[1]for都不应包含任何特定值。

让我们仔细看一下代码:

int16_t **arr = (int16_t **)malloc(num_rows * sizeof(int16_t *));

这将分配一个内存块,足够大以容纳每一行一个指针。变量arr指向此内存。内存块中包含垃圾。

for(i=0; i<num_rows; i++) {
    arr[i] = (int16_t *)malloc(num_cols * sizeof(int16_t));
}     

这将arr[0]设置为指向一个足以容纳一行的内存块。在执行此for循环之前,arr[0]和数组中的其他条目仅包含垃圾。


也许图表会有所帮助。在循环之前的arr中,在第一个分配之后,您将拥有以下内容:

       +--------+
arr -> | arr[0] | -> points to some arbitrary location
       +--------+
       | arr[1] | -> points to some arbitrary location
       +--------+
       | arr[2] | -> points to some arbitrary location
       +--------+
       | arr[3] | -> points to some arbitrary location
       +--------+

那些指针将指向任意位置,因为malloc分配了内存,但将其初始化为任何东西。

这就是您正在检查所有内容的状态,因此每个arr[]可以是任何值。循环一次后,您将拥有:

       +--------+    +----------------+
arr -> | arr[0] | -> | arr[0][0..511] |
       +--------+    +----------------+
       | arr[1] | -> points to some arbitrary location
       +--------+
       | arr[2] | -> points to some arbitrary location
       +--------+
       | arr[3] | -> points to some arbitrary location
       +--------+

只有在 点,您的第二级分配才开始指向有用的东西。

答案 1 :(得分:0)

您的代码没有问题。问题在于您对malloc的工作方式的了解。

当通过malloc动态分配内存时,字节的值是不确定的,因此它们可以采用任何值,包括0。不能保证包含任何特定值。 / p>

在您的示例中:

int16_t **arr = (int16_t **)malloc(num_rows * sizeof(int16_t *));

如果我们假设一个指针占用8个字节,并且num_rows为4,则它将分配32个字节的空间,足以容纳类型int16_t *的4个值。此时,数组的每个成员均不包含任何有意义的值。当您稍后执行此操作时:

arr[i] = (int16_t *)malloc(num_cols * sizeof(int16_t));

您将malloc返回的有效内存地址分配给(假设调用成功)该数组的成员,从而覆盖了以前碰巧存在的所有垃圾值。如果您现在要查看arr[i]的元素,即arry[i][0]arry[i][1]等,那么这些值在您为其指定内容之前也是不确定的。

答案 2 :(得分:0)

您已经接受了答案(正确的是,因为它详细解释了问题所在)。我知道这一点是因为我为此做出了贡献:-)

但是,我还想提出另一种分配2D数组的方法。作为一系列分配来完成它意味着您要在处理完数组后负责清理单个分配。

尽管这可以通过另一个函数来完成,但是您必须将行数传递给其中,以便在释放指针数组分配之前可以正确地释放每个行分配。

我过去使用的一种方法是为指针数组所有行数组分配足够大的 个内存块,然后对其进行按摩它看起来像普通的2D数组(因此array[row][col] = 7之类的东西仍然可以工作)。

下面的代码可以做到这一点,唯一的要求是int16_t的对齐要求不比int16_t*的对齐要求严格(这种情况很少见,您可以解决,但是可能在绝大多数环境中都没有必要):

#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>

void *create_2d_array(uint8_t num_rows, uint16_t num_cols, int clear_data) {
    // Create a single block big enough for both level-1 pointer array
    // and level-2 value array.

    size_t totalMemSz =
        (num_rows * sizeof(uint16_t*)) +            // pointers.
        (num_rows * num_cols * sizeof(uint16_t));   // values.
    void *memBlock = clear_data ? calloc(totalMemSz, 1) : malloc(totalMemSz);
    if (memBlock == NULL) return NULL;

    // Populate the level-1 pointers to point at the level-2 rows.

    for (size_t i = 0; i < num_rows; ++i) {
        ((int16_t**)memBlock)[i] = (int16_t*)(&(((char*)memBlock)[
            num_rows * sizeof(uint16_t*) +    // skip pointers.
            i * num_cols * sizeof(uint16_t)   // select row.
        ]));
    }

    return memBlock;
}

#include <stdio.h>
#include <stdbool.h>

void dumpArr(int16_t **addr, size_t num_rows, size_t num_cols) {
    for (size_t i = 0; i < num_rows; ++i) {
        printf("Pointer[%zd] = %p, data =", i, addr[i]);
        for (size_t j = 0; j < num_cols; ++j) {
            printf(" %2d", addr[i][j]);
        }
        putchar('\n');
    }
}

int main() {
    puts("Running ...");
    int16_t **arr = create_2d_array(4, 7, true);
    arr[0][0] = 1;
    arr[1][2] = 42;
    arr[3][6] = 77;
    dumpArr(arr, 4, 7);
    free(arr);
}

该代码是create_2d_array函数的完整测试程序,因此您可以按自己的意愿进行测试。重要的是,当您完成2D数组后,只需使用free(arr)释放它,而不必进行任何与尺寸有关的特殊处理。

当前代码的示例运行:

Running ...
Pointer[0] = 0x675440, data =  1  0  0  0  0  0  0
Pointer[1] = 0x67544e, data =  0  0 42  0  0  0  0
Pointer[2] = 0x67545c, data =  0  0  0  0  0  0  0
Pointer[3] = 0x67546a, data =  0  0  0  0  0  0 77

答案 3 :(得分:-1)

您的代码没有什么真正错误的。我在下面有几点要说的。

  

不是应该用malloc来确定是否可以分配所请求的大小,否则不能返回NULL指针?

是的,是的。但是您没有测试任何返回值,那么您怎么知道?

此外,使用变量而不是typeof sizeof是一个好习惯。这样可以减少代码重复。因此,写T *t= malloc(n * sizeof(*t))而不是T *t = malloc(n * sizeof(T))。除非您要使用C ++编译器来编译C代码,否则强制转换是完全不必要的,并且不会带来任何好处。

因此,考虑到所有这些,我将编写如下代码:

int16_t** create_2d_array(uint8_t num_rows, uint16_t num_cols)
{
    uint8_t i = 0;  

    int16_t **arr = malloc(num_rows * sizeof(*arr));
    if(!arr) {
        fprintf(stderr, "Error allocating memory\n");
        exit(EXIT_FAILURE);
    }    
    for(i=0; i<num_rows; i++) {
        arr[i] = malloc(num_cols * sizeof(*arr[0]));
        if(!arr[i]) {
            fprintf(stderr, "Error allocating memory\n");
            exit(EXIT_FAILURE);
        }    
    }        

    return arr;     
}

而且,如果您想成为真正的铁杆,并能够像测试malloc一样测试自己的功能,那么您可以这样做。这是goto

的少数几种公认用法之一
int16_t** create_2d_array(uint8_t num_rows, uint16_t num_cols)
{
    uint8_t i = 0;

    int16_t **arr = malloc(num_rows * sizeof(*arr));
    if(!arr)
        goto cleanup1;

    for(i=0; i<num_rows; i++) {
        arr[i] = malloc(num_cols * sizeof(*arr[0]));
        if(!arr[i])
            goto cleanup2;
    }

    return arr;

cleanup2:
    do {
        free(arr[i]);
    } while(i-- > 0);

cleanup1:
    free(arr);

    return NULL;
}

然后您可以执行以下操作:

int16_t ** array2d = create_2d_array(5,6);
if(!array2d) { /* Handle error */ }