动态结构分配后如何修复堆损坏?
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();
}
我正在尝试动态分配一个矩阵作为一个连续的内存块
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();
}