0

I'm trying to use the getdelim function to read an entire text file's contents into a string.

Here is the code I am using:

ssize_t bytesRead = getdelim(&buffer, 0, '\0', fp);

This is failing however, with strerror(errno) saying "Error: Invalid Argument"

I've looked at all the documentation I could and just can't get it working, I've tried getline which does work but I'd like to get this function working preferably.

buffer is NULL initialised as well so it doesn't seem to be that
fp is also not reporting any errors and the file opens perfectly

EDIT: My implementation is based on an answer from this stackoverflow question Easiest way to get file's contents in C

Community
  • 1
  • 1
Arcana
  • 239
  • 5
  • 13
  • 2
    You know you're passing NULL (as 0) for the second parameter. [Check the documentation](http://pubs.opengroup.org/onlinepubs/9699919799/functions/getdelim.html), because I don't believe that is allowed. The function expects a `size_t*`; the value *it points to* can be `0`, but the pointer itself cannot be so. And `buffer` holds the address of dynamic buffer allocated from the `malloc` family`, it should be `NULL` on input as well. – WhozCraig Nov 10 '14 at 03:20
  • I tried that but it still gave the same. It ignores the size value if the first argument is NULL. – Arcana Nov 10 '14 at 03:21
  • It ignores the value **at** the size address; it doesn't ignore the address itself. It will still use the address to store the output size. You're actually using a null char (0-octet) as you delimiter? really? – WhozCraig Nov 10 '14 at 03:23
  • In the getdelim examples I found, 0 seemed to work fine for them. I did try with the size_t* pointer as well (that's how I got getline working). getdelim doesn't respond the same however. There are no compiler errors or warnings, even with -Wall set – Arcana Nov 10 '14 at 03:24
  • As for the delimiter, I'm open to suggestions as to better ways to do it. My goal is the entire file in one string and others have suggested that as a good way. (All files are text files) – Arcana Nov 10 '14 at 03:32
  • Terrific, but as I said, it isn't valid to pass NULL for the second parameter. I've never even bothered trying because the docs never say it is allowed. In fact quite the opposite, the [man page](http://man7.org/linux/man-pages/man3/getline.3.html) are explicit: "In either case, on a successful call, `*lineptr` and `*n` will be updated to reflect the buffer address and allocated size respectively." That alone is enough for me to avoid doing so. – WhozCraig Nov 10 '14 at 03:34
  • And i'll admit this is the first time I've ever seen someone try to use `getdelim` passing a `'\0'` as a delimiter. I see nothing in the docs that say it *shouldn't* work, though, regardless of the oddity. – WhozCraig Nov 10 '14 at 03:35
  • Well there seems to be very little information on it. Especially practical examples. I tried with the 0 as second parametre and also made a size_t *len variable initalised to 0 as well but that didn't work either... – Arcana Nov 10 '14 at 03:37
  • When you did so, the `char**` was *definitely* addressing a `NULL` pointer, right? Ex: `char * lineptr=NULL; size_t n=0; ... getdelim(&lineptr, &n, '\0', fp);` Btw, that exact line could replace the `'\0'` with `'\n'` and it should behave *identically* to `getline`, which you say "worked". This question needs an [MCVE](http://stackoverflow.com/help/mcve) and a sample data file, because something else is wrong if your setup is correct. – WhozCraig Nov 10 '14 at 03:39
  • Yes, that's exactly what I used, the only exception that lineptr was called buffer. I used that code to get getline working but I wanted the whole file into a single string, hence the getdelim and '\0' delimiter – Arcana Nov 10 '14 at 03:42
  • Show your minimal **complete** example. – n. m. could be an AI Nov 10 '14 at 04:00
  • 1
    [Not reproducible here](http://ideone.com/Yw5q6Z). – n. m. could be an AI Nov 10 '14 at 04:20

1 Answers1

2

Kervate, please enable compiler warnings (-Wall for gcc), and heed them. They are helpful; why not accept all the help you can get?

As pointed out by WhozCraig and n.m. in comments to your original question, the getdelim() man page shows the correct usage.

If you wanted to read records delimited by the NUL character, you could use

FILE   *input; /* Or, say, stdin */
char   *buffer = NULL;
size_t  size = 0;
ssize_t length;

while (1) {
    length = getdelim(&buffer, &size, '\0', input);
    if (length == (ssize_t)-1)
        break;

    /* buffer has length chars, including the trailing '\0' */
}

free(buffer);
buffer = NULL;
size = 0;

if (ferror(input) || !feof(input)) {
    /* Error reading input, or some other reason
     * that caused an early break out of the loop. */
}

If you want to read the contents of a file into a single character array, then getdelim() is the wrong function.

Instead, use realloc() to dynamically allocate and grow the buffer, appending to it using fread(). To get you started -- this is not complete! -- consider the following code:

FILE   *input; /* Handle to the file to read, assumed already open */
char   *buffer = NULL;
size_t  size = 0;
size_t  used = 0;
size_t  more;

while (1) {

    /* Grow buffer when less than 500 bytes of space. */
    if (used + 500 >= size) {
        size_t new_size = used + 30000; /* Allocate 30000 bytes more. */
        char  *new_buffer;

        new_buffer = realloc(buffer, new_size);
        if (!new_buffer) {
            free(buffer); /* Old buffer still exists; release it. */
            buffer = NULL;
            size = 0;
            used = 0;
            fprintf(stderr, "Not enough memory to read file.\n");
            exit(EXIT_FAILURE);
        }

        buffer = new_buffer;
        size = new_size;
    }

    /* Try reading more data, as much as fits in buffer. */
    more = fread(buffer + used, 1, size - used, input);
    if (more == 0)
        break; /* Could be end of file, could be error */

    used += more;
}

Note that the buffer in this latter snippet is not a string. There is no terminating NUL character, so it's just an array of chars. In fact, if the file contains binary data, the array may contain lots of NULs (\0, zero bytes). Assuming there was no error and all of the file was read (you need to check for that, see the former example), buffer contains used chars read from the file, with enough space allocated for size. If used > 0, then size > used. If used == 0, then size may or may not be zero.

If you want to turn buffer into a string, you need to decide what to do with the possibly embedded \0 bytes -- I recommend either convert to e.g. spaces or tabs, or move the data to skip them altogether --, and add the string-terminating \0 at end to make it a valid string.

Nominal Animal
  • 38,216
  • 5
  • 59
  • 86
  • This worked, thank you very much! My problem with my size_t variable was that I had defined it as size_t *len, not size_t len... Interesting however that I was compiling with -Wall and it wasn't giving any errors or warnings for just using a literal 0. Hence why I was so confused. – Arcana Nov 10 '14 at 11:01