动态结构分配后如何修复堆损坏?

How to fix Heap Corruption after dynamically struct allocation?

我正在尝试动态分配一个矩阵作为一个连续的内存块

typedef struct 
{
    uint32_t m_width_u32;
    uint32_t m_height_u32;
    uint16_t *m_pixels_u16;
}IMG_t;

IMG_t* img = (IMG_t*)malloc(sizeof(IMG_t));

strcpy(lv_path_to_input_image, argv[1]); 
FILE* file = fopen(file_name, "r");

fscanf(file, "%d%d", &(img->m_height_u32), &(img->m_width_u32)

img->m_pixels_u16 = malloc( sizeof(*img->m_pixels_u16) * img->m_height_u32 * img->m_width_u32);

for (unsigned int i = 0; i < img->m_height_u32; ++i)
{
    for (unsigned int j = 0; j < img->m_width_u32; ++j)
    {
        fscanf(file, "%d", img->m_pixels_u16 + img->m_height_u32 * i + j);
    }
}
fclose(file);

if (img->m_pixels_u16 != NULL)
{
    free(img->m_pixels_u16);
}
free(img);

通过此分配,我得到了一个 HEAP CORRUPTION DETECTED 错误,这意味着我使用的内存比我分配的多。

但是,如果我仅将分配的内存再扩展 2 个字节,堆损坏错误将不再存在。

img->m_pixels_u16 = malloc( sizeof(*img->m_pixels_u16) * img->m_height_u32 * img->m_width_u32 +2 ); 

你能帮我理解这种行为吗?

您混淆了高度和宽度: 首先是 i = 0

j 达到 img->m_width_u32 - 1 所以在循环结束时你在 img->m_pixels_u16 + img->m_width_u32 - 1

i 设置为 one 的下一个循环应该是 img->m_pixels_u16 + img->m_width_u32 + j

应该是这样的:

for (unsigned int i = 0; i < img->m_height_u32; ++i)
{
    for (unsigned int j = 0; j < img->m_width_u32; ++j)
    {
        fscanf(file, "%d", img->m_pixels_u16 + img->m_width_u32 * i + j);
    }
}

在 32 位 int 系统上,错误的说明符会导致 UB。

OP 的代码保存到一个 4 字节的位置,而它应该是 2 个字节。这可能会产生良性后果,直到最后一个数组元素写到边界之外并且 nicely 触发 HEAP CORRUPTION DETECTED。 +2 分配隐藏了问题。

使用匹配的说明符并启用所有警告。一个好的编译器,启用良好,会警告 specifier/argument 不匹配并节省时间。

另见关于错误的指数计算

uint16_t *m_pixels_u16;
...
//fscanf(file, "%d", img->m_pixels_u16 + img->m_height_u32 * i + j);
fscanf(file, "%" SCNu16, img->m_pixels_u16 + img->m_width_u32 * i + j);
//            ^^^^^^^^^                           ^^^^^^^^^^^

同样的问题(但 OP 在这里可能会“走运”)

//fscanf(file, "%d%d", &(img->m_height_u32), &(img->m_width_u32)
fscanf(file, "%" SCNu32 "%" SCNu32, &(img->m_height_u32), &(img->m_width_u32)

其他问题

sizeof(*img->m_pixels_u16) * img->m_height_u32 * img->m_width_u32可能会悄悄溢出来。下面提供溢出检测。

unsigned long long n = 1ULL * img->m_height_u32 * img->m_width_u32;
assert(n <= SIZE_MAX/sizeof(*img->m_pixels_u16)); 
n *= sizeof(*img->m_pixels_u16);

顺便说一句:很好地利用 p = malloc(sizeof *p * ...)

img->m_pixels_u16 = malloc( sizeof(*img->m_pixels_u16) * img->m_height_u32 * img->m_width_u32);

这里也推荐使用。

// IMG_t* img = (IMG_t*)malloc(sizeof(IMG_t));
IMG_t* img = malloc(sizeof *img);

不需要测试。 free(NULL) 可以。

//if (img->m_pixels_u16 != NULL)
//{
    free(img->m_pixels_u16);
//}

然而 NULL 测试在 malloc() 之后就很有用了。注意图像大小 可以 合法地为零。

img->m_pixels_u16 = malloc(n);
if (img->m_pixels_u16 == NULL && n > 0) {
  Handle_OutOfMemory();
}