Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can a char * or char ** masquerade as a FILE *?

Tags:

c

file

io

file-io

In C, I often want to handle data read from a file and data read from an array of strings the same way. Usually reading from a file is for production and from strings is for testing. I wind up writing a lot of code like this:

void handle_line(char *line, Things *things) {
    ...
}

Things *read_from_chars(char *lines[]) {
    Things *things = Things_new();

    for (int i = 0; lines[i] != NULL; i++) {
        handle_line(lines[i], things);
    }

    return things;
}

Things *read_from_input(FILE *input) {
    char *line = NULL;
    size_t linelen = 0;

    Things *things = Things_new();

    while (getline(&line, &linelen, input) > 0) {
        handle_line(line, things);
    }

    return things;
}

This is a duplication of effort.

Is there a way I can make an array of strings masquerade as a FILE * pointer? Or vice-versa? Or is there a better pattern for dealing with this problem?

For bonus points: the solution should make char * or char ** usable with the standard file functions like fgets and getline.

like image 391
Schwern Avatar asked Jan 19 '16 22:01

Schwern


3 Answers

You could use a discriminated union that contains a FILE* and a pointer to the array, then write a get_next function that does the right thing with it.

typedef struct {
    enum { is_file, is_array } type;
    union {
        FILE *file;
        struct {
            int index;
            int size;
            char **lines;
        } array;
    } data;
} file_or_array;

char *get_next(file_or_array foa) {
    if (foa.type == is_file) {
        char *line = NULL;
        size_t linelen = 0;
        getline(&line, &linelen, foa.data.file);
        return line;
    } else {
        if (foa.data.array.index < foa.data.array.size) {
            return strdup(foa.data.array.lines[foa.data.array.index++]);
        } else {
            return NULL;
        }
    }
}

The call to strdup() is necessary to make this work consistently. Since getline() returns a newly-allocated string, which the caller needs to free, it also does the same thing when returning a string from the array. Then the caller can safely free it in both cases.

like image 145
Barmar Avatar answered Nov 09 '22 02:11

Barmar


There's a nonstandard function fmemopen that lets you open a char[] for reading or writing. It's available in most versions of GNU libc, I think, and most versions of Linux.

(This lets you read from or write to a single string, not the array of strings you asked about.)

like image 5
Steve Summit Avatar answered Nov 09 '22 01:11

Steve Summit


One of the most powerful ways to handle this is via streams. I use them to hide file/string/serial ports etc

I have rolled my own stream library which I mainly use on embedded systems

the general idea is :-

typedef struct stream_s stream_t;

struct stream_s
{
    BOOL (*write_n)(stream_t* stream,  char* s, WORD n);
    BOOL (*write_byte)(stream_t* stream,  BYTE b);
    BOOL (*can_write)(stream_t* stream);
    BOOL (*can_read)(stream_t* stream);
    BYTE (*read_byte)(stream_t* stream);
    void* context;
};

then you make a whole bunch of functions

BOOL stream_create(stream_t* stream);
BOOL stream_write_n(stream_t* stream, char* s, WORD n);
BOOL stream_can_read(stream_t* stream);
BYTE stream_read_byte(stream_t* stream);

etc

that use those base function call backs.

the context in the stream struct you use to point to a struct for serial, string, file, or whatever you want. Then you have things like file_create_stream(stream_t* stream, char* filename) which will populate the callbacks on stream with the file related functions. Then for strings you have something similar but handles strings

like image 2
Keith Nicholas Avatar answered Nov 09 '22 00:11

Keith Nicholas