【问题标题】:Heap corruption by realloc when shrinking收缩时由 realloc 造成的堆损坏
【发布时间】:2021-12-30 23:05:51
【问题描述】:

我应该构建一个数据库,通过将动态分配的数组移动到数组末尾来“删除”一个元素。当这种元素的块变得足够大时,应该重新分配内存以缩小数组并永久删除那些无用的元素。
错误:
Realloc 正在抛出:
realloc():下一个大小无效
中止

重现错误的步骤(简化): 从界面中选择删除选项。

代码(简化):
在 main() 中:

 printf("\nEnter id to delete:");
                    scanf("%d",&idtoDelete);
                    delete(&log,idtoDelete);

在functions.c中:

int delete(petCatalog *log, int idtoDelete) {
    //search to find which log has the id to delete.
    pet temp;
    int distance = 0;//between size and the pet to be deleted (temp) dld idtoDelete.
    int calibration = 0;
    int i = 0, j = 0;//virginslots from size-1 to thee first that awaits to be deleted dld virgin slots
    for (i = 0; i < log->size; i++) {
        if (log->array[i].id == idtoDelete) {            
            //move toDelete to my temp
            deepcopy_pet(&temp, &log->array[i], YES);
            log->virginSlots = log->size - i;
            //crerate a gap and fill the gap by overwriting
            distance = log->size - idtoDelete;
            calibration = distance + idtoDelete;
            for (j = i + 1; j < calibration; j++) {
                deepcopy_pet(&log->array[j - 1], &log->array[j], NO);                
            }
            log->size--;
            //temp to cap 
            deepcopy_pet(&log->array[log->cap], &temp, YES);        
            log->awaitDelete++; //all good till here
            log->virginSlots = log->cap - log->awaitDelete - log->size;
            if (log->virginSlots <= DOWNLIM) {
                log->cap = log->cap - log->awaitDelete;//meaning dump everything after: size+virginslots
                log->array = realloc(log->array, log->cap * sizeof(pet));
                if (log->array == NULL) {
                    printf("deletion failed to realloc");
                    exit(EXIT_FAILURE);
                }
            } else if (log->virginSlots >= UPLIM) {
                log->array = realloc(log->array, sizeof(pet) * (log->size) + DEFAULTVIRGIN);
                if (log->array == NULL) {
                    printf("\nfailed to realloc cause of UPLIM");
                    exit(EXIT_FAILURE);
                }
                log->cap = log->size + DEFAULTVIRGIN;
            }
        }
        //sort the array by breed again
    }
    return 0;
}

int deepcopy_pet(pet *dest, pet *source, int mallocDest) {          
    if (mallocDest == YES) {
        dest->sex = (char*)malloc(sizeof(char) * STRLIM);
        if (dest->sex == NULL) {
            printf("failed to malloc dest->breed");
            exit(EXIT_FAILURE);
        }     
       
        dest->breed = (char*)malloc(sizeof(char) * STRLIM);
        if (dest->breed == NULL) {
            printf("failed to malloc dest->breed");
            exit(EXIT_FAILURE);
        }
        
        dest->pet_age = (age*)malloc(sizeof(age));
        if (dest->pet_age == NULL) {
            printf("failed to malloc dest->breed");
            exit(EXIT_FAILURE);
        }
    }
    dest->id = source->id;
    strcpy(dest->sex, source->sex);
    strcpy(dest->breed, source->breed);
    dest->pet_age->years = source->pet_age->years;
    dest->pet_age->months = source->pet_age->months;
    return 0;
}

以及head.h中结构的描述:

typedef struct age { 
    int years, months;
} age;

typedef struct pet {
    int id;
    char *sex;
    char *breed;
    age *pet_age;
} pet;

typedef struct petCatalog {
    pet *array;
    int size;//how many of them i got
    int cap;//how big is the array 
    //cap-count = empty_cells;
    int awaitDelete;
    int virginSlots;
} petCatalog;

Valgrind 输出(简化):

==1260== Invalid write of size 8
==1260==    at 0x109621: deepcopy_pet (in /mnt/c/Users/georg/Desktop/projects/strays/a.out)
==1260==    by 0x1094A9: delete (in /mnt/c/Users/georg/Desktop/projects/strays/a.out)
==1260==    by 0x10A507: main (in /mnt/c/Users/georg/Desktop/projects/strays/a.out)
==1260==  Address 0x4a47108 is 8 bytes after a block of size 192 alloc'd
==1260==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==1260==    by 0x10A3D2: main (in /mnt/c/Users/georg/Desktop/projects/strays/a.out)
==1260== 
==1260== Invalid read of size 8
==1260==    at 0x109629: deepcopy_pet (in /mnt/c/Users/georg/Desktop/projects/strays/a.out)
==1260==    by 0x1094A9: delete (in /mnt/c/Users/georg/Desktop/projects/strays/a.out)
==1260==    by 0x10A507: main (in /mnt/c/Users/georg/Desktop/projects/strays/a.out)
==1260==  Address 0x4a47108 is 8 bytes after a block of size 192 alloc'd
==1260==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==1260==    by 0x10A3D2: main (in /mnt/c/Users/georg/Desktop/projects/strays/a.out)
==1260== 
==1260== Invalid write of size 8
==1260==    at 0x10965E: deepcopy_pet (in /mnt/c/Users/georg/Desktop/projects/strays/a.out)
==1260==    by 0x1094A9: delete (in /mnt/c/Users/georg/Desktop/projects/strays/a.out)
==1260==    by 0x10A507: main (in /mnt/c/Users/georg/Desktop/projects/strays/a.out)
==1260==  Address 0x4a47110 is 16 bytes after a block of size 192 alloc'd
==1260==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==1260==    by 0x10A3D2: main (in /mnt/c/Users/georg/Desktop/projects/strays/a.out)
==1260== 
==1260== Invalid read of size 8
==1260==    at 0x109666: deepcopy_pet (in /mnt/c/Users/georg/Desktop/projects/strays/a.out)
==1260==    by 0x1094A9: delete (in /mnt/c/Users/georg/Desktop/projects/strays/a.out)
==1260==    by 0x10A507: main (in /mnt/c/Users/georg/Desktop/projects/strays/a.out)
==1260==  Address 0x4a47110 is 16 bytes after a block of size 192 alloc'd
==1260==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==1260==    by 0x10A3D2: main (in /mnt/c/Users/georg/Desktop/projects/strays/a.out)
==1260== 
==1260== Invalid write of size 8
==1260==    at 0x10969B: deepcopy_pet (in /mnt/c/Users/georg/Desktop/projects/strays/a.out)
==1260==    by 0x1094A9: delete (in /mnt/c/Users/georg/Desktop/projects/strays/a.out)
==1260==    by 0x10A507: main (in /mnt/c/Users/georg/Desktop/projects/strays/a.out)
==1260==  Address 0x4a47118 is 24 bytes after a block of size 192 in arena "client"
==1260== 

valgrind: m_mallocfree.c:305 (get_bszB_as_is): Assertion 'bszB_lo == bszB_hi' failed.

我真的不明白,为什么我应该在每个 malloc 中非法写入 8 个字节 deepcopy_pet() 函数,并在每次分配给这些变量之一时读取 8 个字节 我刚刚分配了?

我的问题是这样形成的,考虑到引用: “堆错误起源于以前的内存分配,但通常发生在一堆这些之后......”。

有什么建议吗?谢谢。

完整的源代码可在 GitHub here 上找到。
附:另外,一份备忘录可以帮助您(如果您愿意)了解删除变量名的算法。无论如何,它都是可选的,因为我希望您专注于堆损坏的解释。

【问题讨论】:

  • 如果您使用调试信息进行编译,例如gcc -g,valgrind 会告诉你错误访问的行号,你应该先修复这些。行号应该可以帮助您破译这是哪个特定的访问权限以及它可能有什么问题。
  • 如果您在此处需要更多帮助,请发送minimal reproducible example,并将其包含在问题中。 “可根据要求提供完整源代码”并不是一个可以在几年后对某人有所帮助的问题的秘诀。
  • @dratenik,很公平,回购现已公开。你只需克隆它,编译 main.c ,选择 1 进入日志,选择 2 删除它,你马上就会看到发生了什么。
  • @BlueDiary9 ,我记得你帮过我一次,你能再做一次吗?
  • @kjohri,你能不能也用这个做点什么?

标签: c malloc heap-memory valgrind realloc


【解决方案1】:

你的代码太复杂了:你为什么要把pet结构放在数组的末尾?只需释放分配的字段,复制元素,如果低于某个阈值,可能会重新分配数组。深度复制在这里是多余的。为age 使用分配的结构似乎也没有必要,并且为字符串分配STRLIM 字节似乎既浪费又不安全,因为您不检查原始字符串长度是否最多为STRLIM-1。你应该改用strdup()

这是一个简化版:

int delete(petCatalog *log, int idtoDelete) {
    //search to find which log has the id to delete.
    for (int i = 0; i < log->size; i++) {
        if (log->array[i].id == idtoDelete) {
            // free the element data
            free(log->array[i].sex);
            free(log->array[i].breed);
            free(log->array[i].pet_age);
            // copy the remaining elements over the deleted entry
            memmove(&log->array[i], &log->array[i + 1], (log->size - i - 1) * sizeof(log->array[i]));
            log->size--;
            if (log->cap - log->size >= DOWNLIM) {
                // shrink the array, cannot fail.
                int new_cap = log->size + 2; // what ever you shrink it to.
                pet *new_array = realloc(log->array, new_cap * sizeof(log->array[0]));
                if (new_array != NULL) {
                    log->array = new_array;
                    log->cap = new_cap;
                }
            }
            // no need to sort after deleting an element
            return 1;  // element was deleted
        }
    }
    return 0;  // element was not found
}

【讨论】:

  • 感谢您的关注。请注意,我不想释放这些指针,因为我将不得不再次对它们进行 malloc。我希望元素能够随时被覆盖,从而保持功能。我的代码并不复杂,如果你不注意,我的算法很难理解。我也必须使用这些类型的结构,因为我被指示这样做。
  • 抱歉坚持:您多次使用deepcopy_pet 克隆结构,导致大量开销和内存泄漏,只是为了避免重新分配您删除的单个元素?这是矫枉过正和不正确的。如果您想减少对malloc() 的调用,请使用sexbreed 字符数组而不是指针和age 内联结构,删除条目时无需分配或释放任何内容。跨度>
  • 代码太长太复杂。重新格式化重新读取后,错误似乎在这里:deepcopy_pet(&amp;log-&gt;array[log-&gt;cap], &amp;temp, YES); 它将删除的元素存储在分配数组的末尾之外。请注意,您永远不会释放为temp 分配的内存,从而导致内存泄漏。我强烈建议您简化设计并首先集中精力让整个程序正常工作。使用valgrind 检查内存泄漏和损坏。
  • 我听从了你的建议,用 memcpy 替换了 deepcopy_pet() (这是内存泄漏)。我的问题是我仍然收到相同的“invalid next size error”,但现在我的程序只能在 valgrind 中正常工作!这怎么可能……
  • 你使用memcpy还是memmove?如果源对象和目标对象重叠,则必须使用memmove()。你也可以写temp = log-&gt;array[i];。您的代码中的主要问题是您将temp 复制到log-&gt;array[log-&gt;cap],这是不正确的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-02-23
  • 2021-02-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-04
相关资源
最近更新 更多