尝试动态重新分配二维数组时出现段错误

问题描述

我正在研究一个系统,该系统具有多个相互交互的结构 area,这些区域存储在一个名为 storage 的常规数组中,每当您需要添加删除它们。 我处理交互的方式是使用一个名为 overlap 的二维数组,它存储 1 个字节的值。存储的每个元素都有一列和一行。 overlap[x][y] 处的值表示元素 storage[x] 与元素 storage[y] 的交互。

这些区域还具有 layerslayerMasks,用于控制它们可以与哪些元素进行交互。 例如,第 1 层中带有掩码 2、3 和 4 的区域。只能与第 2、3 和 4 层中的区域交互。并且只能与带有掩码 1 的区域交互。层的范围从 0 到63.

为了做到这一点,我需要将存储区域内的区域定位并以能够区分层的方式重叠,为此,我将使用数组 sPos那代表存储位置。这个数组将有 65 个元素,每层一个一个。 sPos 中的值是存储中第一个区域在等于或大于 sPos 值的索引的层中的位置,sPos[64] 是存储的大小。

这就是我处理事情的方式:

area * addArea(area * toAdd) {
// realocating the storage and overlap.
    storage = realloc(storage,sizeof(area *) * (sPos[64] + 1));
    if (!storage) {error handling} // Error handling is a printf("addArea\n") and a return NULL.
    
    overlap = realloc(overlap,sizeof(unsigned char *) * (sPos[64] + 1));                       
    if (!overlap) {error handling}

// Realloc works as malloc for NULL pointers,so setting this to NULL will allocate it when reallocating the rows.
    overlap[sPos[64]] = NULL;      

// Moving the elements in layers greater than or equal to toAdd->layer.
    for (int i = sPos[64]; i > sPos[toAdd->layer]; i--) overlap[i + 1] = overlap[i];                                              
    
// reallocating the rows of the overlap,and moving their elements as well.
    for (int i = 0; i < sPos[64]; i++) {                                                 
        overlap[i] = realloc(overlap[i],sizeof(unsigned char) * sPos[64] + 1);
        if (!overlap[i]) {error handling}
        
        for (int j = sPos[64]; j > sPos[toAdd->layer]; j--) overlap[i][j + 1] = overlap[i][j];
    }

// Seting the new elements of overlap to 0 (no interaction).
    for (int i = 0; i <= sPos[64]; i++) {
        overlap[sPos[toAdd->layer]][i] = 0;
        overlap[i][sPos[toAdd->layer]] = 0;
    }

// Moving the elements in storage to place toAdd in the position sPos[toAdd->layer]
    for (int i = sPos[64]; i > sPos[toAdd->layer]; i--) storage[i] = storage[i - 1]; 
    
    storage[sPos[toAdd->layer]] = toAdd;

// Adding 1 to every element of sPos with an index greater than toAdd->layer.
    for (int i = toAdd->layer + 1; i <= 64; i++) sPos[i]++;
    
    return toAdd; // returns the argument,or NULL in case of error.
}

添加具有不同层的区域时,似乎没有什么不好的事情发生。但是当我尝试在同一层中添加区域时,我得到了一个没有调用错误警告的段错误。通常当有 4 个或更多元素并试图在一个被占用的层中添加一个时。

使用gdb,我认为在重新分配重叠行时会发生错误,但我不太明白为什么。

解决方法

首先,为了提高代码的可读性和可调试性,不要尝试将语句与 for 循环声明在同一行内联。

也就是说,不要这样做:

for (int i = 0; i < N; i++) doSomething(i);

这样做:

for (int i = 0; i < N; i++) {
    doSomething(i);
}

当在调试器中逐行执行时,上面的内容更易于使用。

回到手头的原始问题。您的内存损坏问题。

你分配了这个:

overlap = realloc(overlap,sizeof(Unsigned char *) * (sPos[64] + 1));

让我们假设 sPos[64] 等于 10。因此,您分配了 10+1 == 11 个字节。

因此,overlap 的有效数组索引值来自 [0..10] 包括在内。

然后按如下方式初始化数组:

for (int i = sPos[64]; i > sPos[toAdd->layer]; i--) {
    overlap[i + 1] = overlap[i]; 
}

在 for 循环中执行的第一条语句将是:

overlap[11] = overlap[10];

糟糕! overlap[11] 超出范围。因此,当您写入该内存位置时,未定义行为。您可能损坏了堆。

你可能想要这样的东西(我正在假设你真正想做的事情)

int lastIndex = sPos[64];
int firstIndex = toAdd->layer + 1;
for (int i = lastIndex; i >= firstIndex; i--) {
    overlap[i] = overlap[i-1]; 
}

此外,您可以使用 memmove 为您完成这项工作,前提是您正确计算了指针数学。 (同样,我对您的数组边界进行了假设):

memmove(overlap+firstIndex+1,overlap+firstInex,lastIndex-firstIndex);

我还要指出,当您尝试将这个数组向右移动时,绝对不能保证 overlap[i-1] 不指向垃圾。如果您的 realloc 大小小于或等于该数组的原始分配长度,则没问题。但是,如果它“增长”了数组,您应该假设 realloc 返回了一个全新的数组,而原始的 overlap 数组已被废弃。

我的总体建议是,当您在循环中使用它们时,您应该了解分配的每个数组的有效数组索引是什么。这很可能不是您唯一的“失误 1”。