Search code examples
clinked-listdynamic-memory-allocation

Dynamic allocation of a string in a linked list problem


I have created 2 functions that read some data from a file and write the data in another file, but using linked lists and dynamically allocated strings in that list, but I have a logic error that I can't find:

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

struct product {
    char id[6];
    char *name;
    int price;
    struct product *next;
};

struct product *read(struct product *head, FILE *input) {
    struct product *p, *q, *aux;
    p = (struct product *)malloc(sizeof(struct product));
    char aux_id[6];
    char aux_name[20];
    int aux_price;
    fscanf(input, "%s %s %d", aux_id, aux_name, &aux_price);
    strcpy(p->id, aux_id);
    p->name = (char *)malloc(strlen(aux_name) * (sizeof(char)));
    strcpy(p->name,aux_name);
    p->price = aux_price;
    p->next = NULL;
    head = p;

    while (fscanf(input, "%s %s %d", aux_id, aux_name, &aux_price) != EOF) {
        q = (struct product *)malloc(sizeof(struct product));
        q->name = (char *)malloc(strlen(aux_name) * (sizeof(char)));
        q->next = NULL;
        strcpy(q->name, aux_name);
        strcpy(q->id, aux_id);
        q->price = aux_price;
        p->next = q;
        p = q;
    }
    return head;
}

void write(struct product *head, FILE *output) {
    struct product *p;
    p = head;
    while (p != NULL) {
        fprintf(output, "%s %s %d\n", p->id, p->name, p->price);
        p = p->next;
    }
}

int main() {
    struct product *head, *p, *q;
    FILE *input = fopen("input.txt", "r+");
    FILE *output = fopen("output.txt", "w+");
    head = read(head, input);
    write(head, output);
    fclose(input);
    fclose(output);
}

input file looks like this:

333444 Cola 3
332312 Pepsi 4
123451 Mountain 3

output file looks like this

333444 Cola 3
332312°)q   4
123451à)q   3

Solution

  • There are multiple problems in your code:

    • the arrays into which you read the strings are too small: 6 bytes is not enough to store a 6 digit id, you need space for the null terminator. Same problem in the structure definition.

    • since you do not provide the maximum number of bytes to store into these arrays, fscanf() stores the null terminator beynd the end of the aux_id array, causing undefined behavior which explains the output.

    • the argument head is unused in function read and passed uninitialized in main().

    • You do not allocate enough space for the null terminator. You should either use:

      p->name = malloc(strlen(aux_name) + 1);
      strcpy(p->name, aux_name);
      

    or simply:

      p->name = strdup(aux_name);
    
    • Also note that naming your functions read and write might cause problems as these names are used in the C library for system call wrappers.
    • opening the files for update more is unnecessary: just use "r" and "w".
    • you do not check for fopen() or malloc() failures.

    Here is a modified version:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    struct product {
        char id[7];
        char *name;
        int price;
        struct product *next;
    };
    
    struct product *read(FILE *input) {
        struct product *head = NULL, *tail = NULL, *p;
        char buf[100];
        char aux_id[7];
        char aux_name[20];
        int aux_price;
    
        while (fgets(buf, sizeof buf, input)) {
            if (sscanf(buf, "%6s %19s %d", aux_id, aux_name, &aux_price) != 3) {
                fprintf(stderr, "line format error: %s", buf);
                continue;
            }
            p = (struct product *)malloc(sizeof(struct product));
            if (p == NULL) {
                fprintf(stderr, "memory allocation failure");
                break
            }
            strcpy(p->id, aux_id);
            p->name = strdup(aux_name);
            if (p->name == NULL) {
                fprintf(stderr, "memory allocation failure");
                free(p);
                break;
            }
            p->price = aux_price;
            p->next = NULL;
            if (head == NULL) {
                head = p;
            } else {
                tail->next = p;
            }
            tail = p;
        }
        return head;
    }
    
    void write(struct product *head, FILE *output) {
        struct product *p = head;
        while (p != NULL) {
            fprintf(output, "%s %s %d\n", p->id, p->name, p->price);
            p = p->next;
        }
    }
    
    int main() {
        struct product *head;
        FILE *input = fopen("input.txt", "r");
        if (input == NULL) {
            fprintf(stderr, "cannot open input file\n");
            return 1;
        }
        FILE *output = fopen("output.txt", "w");
        if (output == NULL) {
            fprintf(stderr, "cannot open output file\n");
            return 1;
        }
        head = read(head, input);
        write(head, output);
        fclose(input);
        fclose(output);
        return 0;
    }
    

    strdup() is a POSIX function that is available on most systems and will be included in the next version of the C Standard. If your system does not have it, you can write it this way:

    #include <stdlib.h>
    #include <string.h>
    
    char *strdup(const char *s) {
        size_t size = strlen(s) + 1;
        char *p = malloc(size);
        if (p == NULL)
            return NULL;
        return memcpy(p, s, size);
    }