c - 动态数组和结构 C 的 Valgrind 内存泄漏

标签 c arrays memory memory-leaks

我的代码有问题。代码在没有警告或错误的情况下编译和运行,并且做了我想要的,但是 Valgrind 发现了我无法修复的内存泄漏。我好几天都无法自己更正代码。你能指出我做错了吗?

这是练习,source.c是我自己编的代码,其他的都已经给了。我的想法是我需要创建在连续槽中包​​含 Student 结构的动态数组。

据我所知,Valgrind 认为在 create_student malloc 中为数组条目分配的内存太少,然后测试函数(我们大学的服务器端),同时将我的名字字符串与它以某种方式指向 NULL 指针并超出内存边界。

我尝试了几种方法,编译器 (Code::Blocks) 没有给出任何错误或警告,并且按预期工作,但 Valgrind 仍然不喜欢它..

提前致谢!

Valgrind 输出:

==358== Invalid read of size 8
==358==    at 0x4016CB: test_create_student (test_source.c:38)
==358==    by 0x406340: srunner_run_all (in /tmc/test/test)
==358==    by 0x40288A: tmc_run_tests (tmc-check.c:121)
==358==    by 0x402544: main (test_source.c:310)
==358==  Address 0x518d768 is 0 bytes after a block of size 56 alloc'd
==358==    at 0x4C244E8: malloc (vg_replace_malloc.c:236)
==358==    by 0x402C5D: create_student (source.c:18)
==358==    by 0x401690: test_create_student (test_source.c:35)
==358==    by 0x406340: srunner_run_all (in /tmc/test/test)
==358==    by 0x40288A: tmc_run_tests (tmc-check.c:121)
==358==    by 0x402544: main (test_source.c:310)
==358== 
==358== Invalid read of size 1
==358==    at 0x4C25D94: strcmp (mc_replace_strmem.c:426)
==358==    by 0x4016DC: test_create_student (test_source.c:38)
==358==    by 0x406340: srunner_run_all (in /tmc/test/test)
==358==    by 0x40288A: tmc_run_tests (tmc-check.c:121)
==358==    by 0x402544: main (test_source.c:310)
==358==  Address 0x0 is not stack'd, malloc'd or (recently) free'd
==358== 
==358== 
==358== Process terminating with default action of signal 11 (SIGSEGV)
==358==  Access not within mapped region at address 0x0
==358==    at 0x4C25D94: strcmp (mc_replace_strmem.c:426)
==358==    by 0x4016DC: test_create_student (test_source.c:38)
==358==    by 0x406340: srunner_run_all (in /tmc/test/test)
==358==    by 0x40288A: tmc_run_tests (tmc-check.c:121)
==358==    by 0x402544: main (test_source.c:310)
==358==  If you believe this happened as a result of a stack
==358==  overflow in your program's main thread (unlikely but
==358==  possible), you can try to increase the size of the
==358==  main thread stack using the --main-stacksize= flag.
==358==  The main thread stack size used in this run was 8388608.

这是源文件:

#include "source.h"
#include "string.h"
#include "stdlib.h"

 /* Parameters:
 * s: pointer to the Students main structure (allocated by caller)
 * name: name of student
 * id: Student ID
 * age: age
 * course: course code
 * Returns: pointer to the student element in the array
 */
Student *create_student(Students *s, const char *name, const char *id,
        unsigned char age, const char *course)
{
    if(s->count == 0){
        s->array = malloc(sizeof(Student));
        if(s->array == NULL)
            return NULL;
    }
    if(s->count > 0){
        Student *nptr = realloc(s->array, (s->count+1)*sizeof(Student));
        if(nptr == NULL)
            return NULL;
        else
            s->array = nptr;
    }

    s->array[s->count].name = malloc((strlen(name)+1));                 /*Allocating memory for name pointer*/
    if(s->array[s->count].name == NULL)
        return NULL;
    memcpy(s->array[s->count].name, name, strlen(name));               /*Copying name string to the *name array*/
    s->array[s->count].name[strlen(name)] = '\0';

    if(strlen(id) > 8|| strlen(course) > 16)                            /*Checks if the strings are correct length*/
        return NULL;
    memcpy(s->array[s->count].id, id, strlen(id));                   /*Copying rest of the files*/
    s->array[s->count].id[strlen(id)] = '\0';
    memcpy(s->array[s->count].course, course, strlen(course));
    s->array[s->count].course[strlen(course)] = '\0';
    s->array[s->count].age = age;
    s->array[s->count].points = NULL;
    s->array[s->count].numPoints = 0;

    s->count++;                                                         /*Count is incremented by one as the first student is created*/
    s->array = &s->array[0];                                            /*Now *array points at the first entry in the array*/

    return &s->array[s->count];                                         /*Returns newly created pointer to the student*/
}


/* Parameters:
 * s: pointer to the Students main structure
 * id: Student ID to be looked for
 * course: Course code to be looked for
 * Returns: pointer to the student element in array, if found. NULL if not found
 */
Student *find_student(Students *s, const char *id, const char *course)
{
    Student *ptr = s->array;                                /*Creating pointer that points at the current student structure (starts from the beginning)*/
    int r1, r2;
    for(unsigned int i = 0; i < s->count; i++){
        r1 = strcmp(ptr->course, course);                   /*Checks for ID and course number*/
        r2 = strcmp(ptr->id, id);
        if(r1 == 0 && r2 == 0){
            return ptr;                                     /*Returns pointer to the student structure if matches*/
        }
        ptr++;
    }
    return NULL;                                            /*Returns NULL if no such student found*/
}

/* Parameters:
 * s: pointer to the Students main structure
 * id: Student ID to be deleted
 * course: Course from which student is deleted
 * Returns: 1 if deletion was successful, 0 if not (e.g. student not found)
 */
int delete_student(Students *s, const char *id, const char *course)
{
    Student *st0 = s->array;                                        /*Pointer at the first element in the array*/
    Student *stl = st0;                                             /*Pointer at the last element in the array*/
    for(int i = 0; i < (s->count-1); i++)
        stl++;
    if(find_student(s, id, course) == st0){                         /*1. Deleting the first element in the array*/
        s->count--;
        free(st0->name);
        for(int i = 0; i < s->count; i++)
            memcpy(st0+i, st0+(i+1), sizeof(Student));
        s->array = realloc(s->array, (s->count)*sizeof(Student));
        return 1;
    }
    else if(find_student(s, id, course) == stl){                    /*2. Deleting the last element in the array*/
        s->count--;
        free(stl->name);
        s->array = realloc(s->array, (s->count)*sizeof(Student));
        return 1;
    }
    else if(find_student(s, id, course) != NULL){                   /*3. Deleting the element in the middle of array*/
        int a = 0;
        while(st0 != find_student(s, id, course)){
            a++;
            st0++;
        }
        free(st0->name);
        s->count--;
        for(int i = 0; i < s->count-a; i++){
            memcpy(st0+i, st0+(i+1), sizeof(Student));
        }
        s->array = realloc(s->array, (s->count)*sizeof(Student));
        return 1;
    }
    return 0;
}


/* Parameters:
 * s: pointer to the Students main structure
 * id: student ID to set the points
 * course: course ID to set the points
 * points: array of points to be set to the student (will replace previous entry)
 * len: length of the points array
 * Returns: 1 if setting points was successful, 0 if not (e.g. student not found)
 */
int set_points(Students *s, const char *id, const char *course, const float *points, int len)
{
    Student *st = find_student(s, id, course);
    if(st != NULL){
        if(st->points == NULL){
            st->points = malloc(len * sizeof(int));
            for(int i = 0; i < len; i++)
                st->points[i] = points[i];
            st->numPoints = len;
            return 1;
        }
        else{
            st->points = realloc(st->points, len * sizeof(int));
            for(int i = 0; i < len; i++)
                st->points[i] = points[i];
            st->numPoints = len;
            return 1;
        }
    }
    return 0;
}

这是 source.h:

typedef struct student Student;

struct student {
    char *name; // name of the student
    char id[8]; // null-terminated student ID
    unsigned char age;
    char course[16]; // null-terminated course code;
    float *points; // pointer to dynamic array of exercise points
    unsigned int numPoints; // length of the above array
};

typedef struct {
    unsigned int count; // size of the students array
    Student *array; // pointer to the first element in the array
} Students;

Student *create_student(Students *s, const char *name, const char *id,
        unsigned char age, const char *course);
Student *find_student(Students *s, const char *id, const char *course);
int delete_student(Students *s, const char *id, const char *course);
int set_points(Students *s, const char *id, const char *course, const float *points, int len);

这是主要的:

#include <stdio.h>
#include <assert.h>
#include <string.h>
#include "source.h"

void print_students(Students *s)
{
    Student *st = s->array;
    for (unsigned int i = 0; i < s->count; i++) {
        printf("%s (%s), Course: %s, Age: %d\n", st->name, st->id, st->course, st->age);
        if (st->numPoints) {
            printf(" -- Points: ");
            for (unsigned int j = 0; j < st->numPoints; j++)
                printf("%f  ", st->points[j]);
            printf("\n");
        }
        st++;
    }
}

void initialize_reg(Students *reg) {
    assert(reg != NULL);
    reg->count = 0;
    reg->array = NULL;
    create_student(reg, "Teemu Teekkari", "00000A", 20, "ELEC-A1100");
    create_student(reg, "Matti Meikäläinen", "12345B", 28, "ELEC-A1100");
    create_student(reg, "Wow", "33333C", 28, "ELEC-A1100");
    create_student(reg, "Much Student", "98765H", 28, "ELEC-A1100");
    create_student(reg, "Such course", "12121R", 28, "ELEC-A1111");
    create_student(reg, "Amaze", "11111T", 28, "ELEC-A1111");
}

int main()
{
    Students s;
    s.count = 0;
    s.array = NULL;

    // create a group of students using create_student
    initialize_reg(&s);
    print_students(&s);

    // Try find_student 
    Student *sf3 = find_student(&s, "33333C", "ELEC-A1100");
    Student *sf6 = find_student(&s, "11111T", "ELEC-A1111");
    if (!sf3) {
        printf("Did not find existing student 33333C\n");
    } else if (strcmp(sf3->id, "33333C")) {
        printf("Incorrect student ID %s when should have been 33333C\n", sf3->id);
    }
    if (!sf6) {
        printf("Did not find existing student 11111T\n");
    } else if (strcmp(sf6->id, "11111T")) {
        printf("Incorrect student ID %s when should have been 11111T\n", sf6->id);
    }

    // Try delete_student with existing student
    if (!delete_student(&s, "12121R", "ELEC-A1111")) {
        printf("Delete student failed for existing student\n");
    }

    printf("-----\n");
    float p[] = {3.0, 1.0, 4.0, 4.5};
    set_points(&s, "00000A", "ELEC-A1100", p, 4);
    print_students(&s);
    printf("-----\n");

    // Try delete_student with non_existing student
    delete_student(&s, "33333C", "ELEC-A1100");
    print_students(&s);
}

这是 Valgrind 引用的 test_source:

#include <check.h>
#include "tmc-check.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include "../src/source.h"

void release_memory(Students *s) {
    unsigned int i;
    if (s->array) {
        for (i = 0; i < s->count; i++) {
            Student *st = &(s->array[i]);
            if (st->name)
                free(st->name);
            if (st->points)
                free(st->points);
        }
        free(s->array);
    }
}

START_TEST(test_create_student) {
    Students reg;
    //assert(reg != NULL);
    reg.count = 0;
    reg.array = NULL;
    char buf[160];

    //char *name = malloc(strlen("Teemu Teekkari") + 1);
    //strcpy(name, "Teemu Teekkari");
    char *name = "Teemu Teekkari";

    Student *s1 = create_student(&reg, name, "00000A", 20, "ELEC-A1100");
    fail_unless(s1 != NULL, "[Task 3.4.a] create_student returned NULL.\n");

    if (strcmp(name, s1->name)) {
        sprintf(buf, "[Task 3.4.a] Student name should be %s, was %s.\n", name, s1->name);
        release_memory(&reg);
        fail(buf);
    }

    if (strcmp("00000A", s1->id)) {
        sprintf(buf, "[Task 3.4.a] Student ID should be %s, was %s.\n", "00000A", s1->id);
        release_memory(&reg);
        fail(buf);
    }

    if (s1->age != 20) {
        sprintf(buf, "[Task 3.4.a] Student age should be 20, was %d\n.", s1->age);
        release_memory(&reg);
        fail(buf);
    }

    if (strcmp("ELEC-A1100", s1->course)) {
        sprintf(buf, "[Task 3.4.a] Course code not should be %s, was %s.\n", "ELEC-A1100", s1->course);
        release_memory(&reg);
        fail(buf);
    }

    if (s1->points != NULL) {
        sprintf(buf, "[Task 3.4.a] Points array should be NULL, was %p.\n", s1->points);
        release_memory(&reg);
        fail(buf);
    }

    if (s1->numPoints != 0) {
        sprintf(buf, "[Task 3.4.a] numPoints should be 0, was %d.\n", s1->numPoints);
        release_memory(&reg);
        fail(buf);
    }

    //free(name);
    //fail_unless(!strcmp("Teemu Teekkari", s1->name), "[Task 17.1] Student name was not allocated from heap.");

    if (reg.count != 1) {
        sprintf(buf, "[Task 3.4.a] After adding a student, student count should be 1, was %d.\n",
                reg.count);
        release_memory(&reg);
        fail(buf);
    }

    if (reg.array != s1) {
        release_memory(&reg);
        fail("[Task 3.4.a] After adding one student, the returned value does not point to the beginning of array.\n");
    }

    Student *s2 = create_student(&reg, "Peppilotta Sikuriina Rullakartiina Kissanminttu Efraimintytar Pitkatossu", "12345B", 10, "ELEC-A1100");
    if ((reg.array) + 1 != s2) {
        release_memory(&reg);
        fail("[Task 3.4.a] After adding second student, the returned value does not point to the second array member.\n");
    }

    if (reg.count != 2) {
        sprintf(buf, "[Task 3.4.a] After adding second student, the student count should be 2, was %d.\n",
                reg.count);
        release_memory(&reg);
        fail(buf);
    }

#if 0
    Student *s3 = create_student(&reg, "Ylimaaraisia Merkkeja", "99999Ffoofoofoofoo", 60, "ELEC-A1111-even-16-characters-is-too-much-for-a-course-code");
    assert(s3 != NULL);

    if (strcmp("99999Ff", s3->id)) {
        sprintf("[Task 3.4.a] Too long student id truncated incorrectly: %s, should be %s\n",
                s3->id, "99999Ff");
        release_memory(&reg);
        fail(buf);
    }
    if (strcmp("ELEC-A1111-even", s3->course)) {
        sprintf(buf, "[Task 3.4.a] Too long course code truncated incorrectly: %s, should be %s\n",
                s3->course, "ELEC-A1111-even");
        release_memory(&reg);
        fail(buf);
    }
#endif
    release_memory(&reg);
}

END_TEST

Students *initialize_reg(Students *reg) {
    //Students *reg = malloc(sizeof(Students));
    assert(reg != NULL);
    reg->count = 0;
    reg->array = NULL;
    create_student(reg, "Teemu Teekkari", "00000A", 20, "ELEC-A1100");
    create_student(reg, "Matti Meikäläinen", "12345B", 28, "ELEC-A1100");
    create_student(reg, "Wow", "33333C", 28, "ELEC-A1100");
    create_student(reg, "Much Student", "98765H", 28, "ELEC-A1100");
    create_student(reg, "Such course", "12121R", 28, "ELEC-A1111");
    create_student(reg, "Amaze", "11111T", 28, "ELEC-A1111");
    return reg;
}

START_TEST(test_find_student) {
    Students regb;
    char buf[160];
    Students *reg = initialize_reg(&regb);
    Student *sf3 = find_student(reg, "33333C", "ELEC-A1100");
    Student *sf6 = find_student(reg, "11111T", "ELEC-A1111");

    if (sf3 == NULL) {
        release_memory(reg);
        fail("[Task 3.4.b] find_student returned NULL for existing student ID %s.\n", "33333C");
    }

    if (strcmp(sf3->id, "33333C")) {
        sprintf(buf, "[Task 3.4.b] find_student returned student with wrong id. Searched for: %s, returned: %s", "33333C", sf3->id);
        release_memory(reg);
        fail(buf);
    }

    if (sf6 == NULL) {
        release_memory(reg);
        fail("[Task 3.4.b] find_student returned NULL for existing student %s.\n", "11111T");
    }

    if (strcmp(sf6->id, "11111T")) {
        sprintf(buf, "[Task 3.4.b] find_student returned student with wrong id. Searched for: %s, returned: %s", "11111T", sf6->id);
        release_memory(reg);
        fail(buf);
    }

    if (NULL != find_student(reg, "98989D", "ELEC-A1112")) {
        release_memory(reg);
        fail("[Task 3.4.b] find_student should return NULL for nonexistent students.\n");
    }

    /*    fail_unless(NULL == find_student(reg, "98989D", "ELEC-A1100"), "[Task 17.2] find_student should return NULL for nonexistent students.");
        fail_unless(NULL == find_student(reg, "33333C", "ELEC-A1112"), "[Task 17.2] find_student should return NULL for nonexistent students.");
        fail_unless(NULL == find_student(reg, "33333C", "ELEC-A1111"), "[Task 17.2] find_student should return NULL for nonexistent students.");*/
    release_memory(reg);
}

END_TEST


START_TEST(test_delete_student) {
    char buf[160];
    Students regb;
    Students *reg = initialize_reg(&regb);
    if (reg->count != 6) {
        sprintf(buf, "[Task 3.4.c] Wrong student count after adding 6 students, you have %d\n.",
                reg->count);
        release_memory(reg);
        fail(buf);
    }

    if (!delete_student(reg, "11111T", "ELEC-A1111")) {
        sprintf(buf, "[Task 3.4.c] delete_student() failed for existing student 11111T.\n");
        release_memory(reg);
        fail(buf);
    }

    if (reg->count != 5) {
        sprintf(buf, "[Task 3.4.c] After deleting one student, student count should be 5, you had %d.\n",
                reg->count);
        release_memory(reg);
        fail(buf);
    }

    if (!delete_student(reg, "33333C", "ELEC-A1100")) {
        sprintf(buf, "[Task 3.4.c] delete_student() failed for existing student 33333C.\n");
        release_memory(reg);
        fail(buf);
    }
    //fail_unless(reg->count == 4, "[Task 17.3] Wrong student count after deletion.");

    assert((reg->array + 2) != NULL);

    if (strcmp((reg->array + 2)->id, "98765H")) {
        sprintf(buf, "[Task 3.4.c] After deleting student 33333C, student %s should be in 3rd array position. You have %s.\n", "98765H", (reg->array + 2)->id);
        release_memory(reg);
        fail(buf);
    }

    if (delete_student(reg, "33330C", "ELEC-A1101")) {
        release_memory(reg);
        fail("[Task 3.4.c] delete_student() should have failed for nonexisting student 33330C.\n");
    }
    /*fail_unless(delete_student(reg, "00000A", "ELEC-A1111") == 0, "[Task 17.3] delete_student() should have failed for nonexisting student.");
    fail_unless(delete_student(reg, "12121R", "ELEC-A1100") == 0, "[Task 17.3] delete_student() should have failed for nonexisting student.");
    fail_unless(delete_student(reg, "00000A", "ELEC-A1100"), "[Task 17.3] delete_student() failed for existing student 00000A.");
    fail_unless(delete_student(reg, "12345B", "ELEC-A1100"), "[Task 17.3] delete_student() failed for existing student 12345B.");
    fail_unless(delete_student(reg, "98765H", "ELEC-A1100"), "[Task 17.3] delete_student() failed for existing student 98765H.");
    fail_unless(delete_student(reg, "12121R", "ELEC-A1111"), "[Task 17.3] delete_student() failed for existing student 12121R.");

    fail_unless(reg->count == 0, "[Task 17.3] Course register should be empty after deleting all students.");*/

    release_memory(reg);
}

END_TEST


void pr_array(char *buf, float *arr, int n) {
    char b[40];
    sprintf(buf, "{");
    while (n--) {
        sprintf(b, "%.1f", *arr++);
        if (n)
            strcat(b, ", ");
        strcat(buf, b);
    }
    strcat(buf, "}");
}

START_TEST(test_set_points) {
    Students regb;
    Students *reg = initialize_reg(&regb);
    char arrbuf[80];
    char buf[160];
    float p[4];
    int i;
    for (i = 0; i < 4; i++) {
        p[i] = (float)(rand() % 10) / 2;
    }
    pr_array(arrbuf, p, 4);

    if (!set_points(reg, "00000A", "ELEC-A1100", p, 4)) {
        sprintf(buf, "[Task 3.4.d] set_points() returned 0 for student 00000A with array %s, but it should have succeeded.\n",
                arrbuf);
        release_memory(reg);
        fail(buf);
    }
    Student *st = &(reg->array[0]);

    if (st->points == NULL) {
        sprintf(buf, "[Task 3.4.d] Points array not created for array %s (is still NULL).\n",
                arrbuf);
        release_memory(reg);
        fail(buf);
    }

    char arr2[80];
    pr_array(arr2, st->points, 4);
    for (i = 0; i < 4; i++) {
        if (st->points[i] != p[i]) {
            sprintf(buf, "[Task 3.4.d] Point array differs. Should be %s. You have %s\n",
                    arrbuf, arr2);
            release_memory(reg);
            fail(buf);
        }
    }
    /*float q[] = {2, 5, 1, 6, 12, 1};
    fail_unless(set_points(reg, "00000A", "ELEC-A1100", q, 6) == 1, "[Task 17.4] set_points() did not return 1 on success or failed when it was not supposed to");
    fail_unless(st->points != NULL, "[Task 17.4] Points array for student not created");
    fail_unless(st->points[4] == 12, "[Task 17.4] Setting points failed on student");
    fail_unless(set_points(reg, "00000A", "ELEC-A1111", p, 3) == 0, "[Task 17.4] set_points() did not return 0 on failure");*/

    release_memory(reg);
}

END_TEST


int main(int argc, const char *argv[]) {
    srand((unsigned) time(NULL));
    Suite *s = suite_create("Test-3.4");

    /* TODO: define tests */
    tmc_register_test(s, test_create_student, "3.4.a");
    tmc_register_test(s, test_find_student, "3.4.b");
    tmc_register_test(s, test_delete_student, "3.4.c");
    tmc_register_test(s, test_set_points, "3.4.d");

    return tmc_run_tests(argc, argv, s);
}

最佳答案

并且有了测试源码,结合我上次的评论,现在很容易看出问题所在:

您返回一个 Student 指针,该指针超出分配的内存。

create_student 函数返回时,您已经增加了 s->count 因此它是“数组”s->array 中的条目数。但是大小数组索引是从零开始的,此时最大索引是 s->count - 1

关于c - 动态数组和结构 C 的 Valgrind 内存泄漏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21908776/

相关文章:

php - 在优化阶段,通过引用传递所有数组是否明智?

c++ - 在重复循环时跳过文本文件行

c++ - 如何对数组 int (*&Test)[10] 进行赋值?

.Net 和非托管 dll -> 内存问题

c - C语言中一个地址减去另一个地址

c - 使用 C 在运行时处理符号查找错误

c - int variable++ 不会递增超过 1 或将重置为 0

java - 减少 Java 进程的内存使用

c - C中String tokenizer的奇怪行为

c - 从 C 调用 Swift 的最佳方式是什么?