使用 malloc 创建的结构数组不会使用 free 和 realloc 删除或调整最后一个元素的大小

array of structs created with malloc is not deleting nor resizing last element using free and realloc

我的代码包含一个应用程序,该应用程序由控制台一个客户端 (clientes) 接收并将其添加到使用 malloc 创建的数组中。在我们可以删除这个客户之后。我们也可以添加新行程 (viajes) 并删除它(过程类似于代码中所示)。

正如我之前所说,我正在尝试删除使用 malloc 创建的结构数组的最后一项。我将向您展示代码(我如何添加新的 clientes 以及我如何尝试删除它)。还要说我得到的错误在产生错误的行中进行了注释,所以我认为它更直观。又说代码是MCVE,所以只有复制代码才会运行。我的应用程序代码是:

#include<stdlib.h>
#include<stdio.h>
#include<string.h>

// DEFINE STRUCTS
struct viaje {
    char *identificador;
    char *ciudadDestino;
    char *hotel;
    int numeroNoches;
    char *tipoTransporte;
    float precioAlojamiento;
    float precioDesplazamiento;
};

struct cliente {
    char *dni;
    char *nombre;
    char *apellidos;
    char *direccion;
    int totalViajes;
    struct viaje *viajes;
};

int main(){
    // Variables
    int i = 0;
    int totalClientes = 0;
    char dni[255];
    char nombre[255];
    char apellidos[255];
    char direccion[255];

    // Init array of struct cliente (in var clientes)
    struct cliente *clientes = (struct cliente *)malloc(0);

    printf("Input number of elements of struct clientes: ");
    scanf("%i", &totalClientes);
    fflush(stdin);

    // ADDING NEW ELEMENTS INTO CLIENTES
    for (i = 0; i < totalClientes; i++) {

        // Receive parameters
        printf("Input NIF: ");
        gets(dni);
        fflush(stdin);
        printf("Input NAME: ");
        gets(nombre);
        fflush(stdin);
        printf("Input SURNAME: ");
        gets(apellidos);
        fflush(stdin);
        printf("Input ADDRESS: ");
        gets(direccion);
        fflush(stdin);

        // Create memory for his child (MAX_TAM_* are #define int)
        clientes = (struct cliente *)realloc(clientes, (i+1)*sizeof(struct cliente));
        clientes[i].dni = (char *)malloc(200*sizeof(char));
        clientes[i].nombre = (char *)malloc(200*sizeof(char));
        clientes[i].apellidos = (char *)malloc(200*sizeof(char));
        clientes[i].direccion = (char *)malloc(200*sizeof(char));
        clientes[i].viajes = (struct viaje *)malloc(0); // Init clientes[i].viajes to 0 as previously done with clientes

        // Adding received element
        strcpy(clientes[i].dni, dni);
        strcpy(clientes[i].nombre, nombre);
        strcpy(clientes[i].apellidos, apellidos);
        strcpy(clientes[i].direccion, direccion);
    /* DANGER - ERROR HERE */
        clientes[i].totalViajes = 0; // HERE I GET A NOT EXPECTED BEHAVIOR - int is not added to clientes[i].totalViajes - Receive NULL
        // New element added sucessfully
    }

    // SHOW ELEMENTS CREATED
    for (i = 0; i < totalClientes; i++) {
        printf("\n");
        printf("%i.\n", i);
        printf("NIF: %s\n", clientes[i].dni);
        printf("NAME: %s\n", clientes[i].nombre);
        printf("SURNAME: %s\n", clientes[i].apellidos);
        printf("ADDRESS: %s\n", clientes[i].direccion);
        printf("TRAVELS_COUNT: %s\n", clientes[i].totalViajes);
       printf("\n");
    }

    // DELETING AN ELEMENT OF CLIENTES
    int posCliente = 0;

    printf("Input position of struct clientes that you wanna delete: ");
    // posCliente is the position (inside the array) of cliente we wanna remove
    scanf("%i", &posCliente);
    fflush(stdin);

    // Rewind one position array clientes since cliente we want to remove
    for (i = posCliente; i < totalClientes-1; i++) {
        // Rewind one element array clientes
        clientes[i] = clientes[i+1];
    }

    //freeing memory of this element (now, the element is the last of the array, we have moved it into last position with previously for)
    free(clientes[totalClientes].dni);
    free(clientes[totalClientes].nombre);
    free(clientes[totalClientes].apellidos);
    free(clientes[totalClientes].direccion);
    clientes[totalClientes].totalViajes = 0;
    // Remove / free memory of the element deleted
/* DANGER - ERROR HERE */
    //free(clientes[totalClientes]); // HERE I GET AN ERROR: `error: incompatible type for argument 1 of 'free'`

    // Now totalClientes is one less (we have deleted one element)
    totalClientes--;

    // Resize array clientes. It must have one less element (now totalClientes is totalClientes-1)
/* DANGER - ERROR HERE */
    //clientes = (struct cliente *)realloc(clientes, (totalClientes)*sizeof(struct cliente)); // HERE I DO NOT GET AN ERROR BUT PROGRAM CRASH

    // SHOW ELEMENTS AFTER DELETING
/* DANGER - ERROR HERE */
    // if the max legnth is totalClientes+1 we can see that the last element removed with free cointinues existing, so free is not freeing memory well
    for (i = 0; i < totalClientes; i++) {
        printf("\n");
        printf("%i.\n", i);
        printf("NIF: %s\n", clientes[i].dni);
        printf("NAME: %s\n", clientes[i].nombre);
        printf("SURNAME: %s\n", clientes[i].apellidos);
        printf("ADDRESS: %s\n", clientes[i].direccion);
        printf("TRAVELS_COUNT: %s\n", clientes[i].totalViajes);
        printf("\n");
    }
}

这里有几个问题。第一:

printf("TRAVELS_COUNT: %s\n", clientes[i].totalViajes);

字段 totalViajes 是一个整数,但您正在使用 %s 进行打印,它期望 char * 指向空终止字符串。值 0 被解释为 NULL 指针,这就是打印 NULL 的原因。使用 %d 打印整数:

printf("TRAVELS_COUNT: %d\n", clientes[i].totalViajes);

其次,当您转到 free 已删除元素的字符串时,您删除了错误的元素。您正在使用索引 totalClientes,它位于数组的末尾。读取数组末尾,以及尝试 free 未从 malloc 接收到的内存,调用 undefined behavior

您可能想对索引为 totalClientes-1 的最后一个元素执行此操作,但这也不正确,因为它包含列表中最后一个元素的字符串。指向您要删除的内存的指针位于索引 posCliente 中,您在移动元素时覆盖了它,从而导致内存泄漏。将对 free 的调用移动到移动元素的循环之前,并使用 posCliente 作为索引进行清理:

// Remove / free memory of the element deleted
free(clientes[posCliente].dni);
free(clientes[posCliente].nombre);
free(clientes[posCliente].apellidos);
free(clientes[posCliente].direccion);

// Rewind one position array clientes since cliente we want to remove
for (i = posCliente; i < totalClientes-1; i++) {
    // Rewind one element array clientes
    clientes[i] = clientes[i+1];
}

最后,您不能在数组的最后一个元素上调用 free,因为 1) 它不是指针,并且 2) 即使您获取了它的地址,该地址也不会返回malloc。你注释掉的realloc就可以了。它崩溃的原因是由于您的程序中较早出现的未定义行为。

此外,fflush(stdin) 无效,gets is insecure and should not be used 因为它可能会让您溢出缓冲区。使用 scanf 代替长度修饰符来指定最大大小:

    printf("Input NIF: ");
    scanf("%254s", dni);
    printf("Input NAME: ");
    scanf("%254s", nombre);
    printf("Input SURNAME: ");
    scanf("%254s", apellidos);
    printf("Input ADDRESS: ");
    scanf("%254s", direccion);