简体   繁体   中英

How can I read any file into a linked list?

I'm supposed to create a program that can read any file into a linked list. This is what I came up with so far:

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

#define MAX_BUFFER_SIZE 1024

typedef struct list {
    char *string;
    struct list *next;
} LIST;

void print_list(LIST *head) {
    LIST *current = head;
    while (current != NULL) {
        printf("%s", current->string);
        current = current->next;
    }
}

void push(LIST **head, FILE **fp) {
    char line[MAX_BUFFER_SIZE];
    LIST *node, *current = *head;

    while(fgets(line, sizeof(line), *fp)) {
        node = malloc(sizeof(LIST));
        if (node == NULL) exit(1);

        node->string = strdup(line);
        node->next = NULL;

        if(current == NULL) {
            *head = node;
            current = node;
        } else {
            current->next = node;
            current = current->next;
        }
    }
}

int main(int argc, char *argv[]) {
    FILE *fp = fopen(argv[1], "r");
    LIST *head = NULL;

    push(&head, &fp);
    fclose(fp);
    print_list(head);
    return 0;
}

When comparing the contents of the linked list with the contents of the input file this comparison succeeds when using a .txt file but fails when using a file with binary data. This suggests that my program changes the contents of the binary file.

What am I doing wrong?

Random binary data can contain characters that are not printable. Or might contain zeroes, which is the string terminator and thus terminate your strings early. Just don't read and write raw binary data as strings or using string functions, it will simply not work as you expect.

If you want to read and write arbitrary data of any kind, use eg fread and fwrite instead, and open your files in binary mode.

Since you are using Linux, you can use POSIX.1 getline() to read lines, including lines with embedded NUL bytes; you do need to write those lines using fwrite() .

For the linked list, you should include a length field for fwrite() . I'd also make the linked list data element a flexible array member:

struct node {
    struct node *next;
    size_t       size;
    char         data[];
    /* Note: data[size+1], data[size] == '\0'.
             This is not necessary for correct operation,
             but allows one to assume there is always at
             least one char in data, and the data is followed
             by a nul byte. It makes further use of this
             structure easier. */
};

struct node *node_new(const char *data, size_t size)
{
    struct node *n;

    n = malloc(sizeof (struct node) + size + 1);
    if (!n) {
        fprintf(stderr, "node_new(): Out of memory.\n");
        exit(EXIT_FAILURE);
    }

    n->next = NULL;
    n->size = size;
    if (size > 0)
        memcpy(n->data, data, size);
    n->data[size] = '\0';

    return n;
}

When reading lines, it is easiest to prepend the lines to the list:

struct node *list = NULL;
struct node *curr;

char   *line = NULL;
size_t  size = 0;
ssize_t len;

while (1) {
    len = getline(&line, &size, stdin);
    if (len < 0)
        break;

    curr = node_new(line, (size_t)len);

    curr->next = list;
    list = curr;
}

list = list_reverse(list);

When done, you reverse the list, to get the first read line at the beginning of the list:

struct node *list_reverse(struct node *curr)
{
    struct node *root = NULL;
    struct node *next;

    while (curr) {
        next = curr->next;

        curr->next = root;
        root = curr;

        curr = next;
    }

    return root;
}

To write each line to a stream, you use for example fwrite(node->data, node->size, 1, stdout) .

If the output stream is not a local file, but a pipe or socket, fwrite() can return a short count. It is not an error; it only means that only part of the data could be written. To cater for those cases, you can use two helper functions: one to ensure all of the data is written, even when writing to a pipe, and another to scan through the list, using the first one to output each line:

static int fwriteall(const char *data, size_t size, FILE *out)
{
    size_t  n;

    while (size > 0) {
        n = fwrite(data, 1, size, out);
        if (n > 0) {
            data += n;
            size -= n;
        } else
            return -1; /* Error */
    }

    return 0; /* Success */
}

int list_writeall(FILE *out, struct node *list)
{
    for (; list != NULL; list = list->next)
        if (list->size > 0)
            if (fwriteall(list->data, list->size, out)
                return -1; /* Error */
    return 0; /* Success */
}

Instead of getline() , you can read chunks of some predefined size using fread() :

struct node *read_all(FILE *in, const size_t size)
{
    struct node *list = NULL;
    struct node *curr;
    size_t       used;

    while (1) {
        curr = malloc(sizeof (struct node) + size + 1);
        if (!curr) {
            fprintf(stderr, "read_all(): Out of memory.\n");
            exit(EXIT_FAILURE);
        }

        size = fread(curr->data, 1, size, in);
        if (used > 0) {
            /* Optional: Optimize memory use. */
            if (used != size) {
                void *temp;
                temp = realloc(curr, sizeof (struct node) + used + 1);
                /* Reallocation failure is not fatal. */
                if (temp) {
                    curr = temp;
                    curr->size = used;
                }
            }
        }
        curr->data[used] = '\0';

        curr->next = list;
        list = curr;
    }

    return list_reverse(list);
}

The function returns the reversed list (ie, with first line first in list). After calling the function, you should check using ferror(in) whether the entire input stream was read, or if there was an error.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM