40

I want to read the contents of a text file into a char array in C. Newlines must be kept.

How do I accomplish this? I've found some C++ solutions on the web, but no C only solution.

Edit: I have the following code now:

void *loadfile(char *file, int *size)
{
    FILE *fp;
    long lSize;
    char *buffer;

    fp = fopen ( file , "rb" );
    if( !fp ) perror(file),exit(1);

    fseek( fp , 0L , SEEK_END);
    lSize = ftell( fp );
    rewind( fp );

    /* allocate memory for entire content */
    buffer = calloc( 1, lSize+1 );
    if( !buffer ) fclose(fp),fputs("memory alloc fails",stderr),exit(1);

    /* copy the file into the buffer */
    if( 1!=fread( buffer , lSize, 1 , fp) )
      fclose(fp),free(buffer),fputs("entire read fails",stderr),exit(1);

    /* do your work here, buffer is a string contains the whole text */
    size = (int *)lSize;
    fclose(fp);
    return buffer;
}

I get one warning: warning: assignment makes pointer from integer without a cast. This is on the line size = (int)lSize;. If I run the app, it segfaults.

Update: The above code works now. I located the segfault, and I posted another question. Thanks for the help.

unwind
  • 391,730
  • 64
  • 469
  • 606
friedkiwi
  • 2,158
  • 8
  • 29
  • 52
  • 1
    Possible duplicate of [Easiest way to get file's contents in C](http://stackoverflow.com/questions/174531/easiest-way-to-get-files-contents-in-c) – Ciro Santilli OurBigBook.com Oct 29 '15 at 12:49
  • 3
    Using fseek() to get the size of the file, limits you to only reading real disk files. Using it means you can not read from a pipe (like standard input), named pipe, devices, or network streams. See the link in the comment above [Easiest way to get file's contents in C](http://stackoverflow.com/questions/174531) – anthony Jan 05 '17 at 05:06
  • Please don't edit answers into questions. Post your own answer if you want a polished version of that. This has bugs like `size = (int *)lSize;` that sets the pointer local variable `size` to an integer cast to a pointer, but doesn't do anything to update the `int` the caller passed a pointer to. (Probably you meant `*size = lSize`). So this buggy answer should be downvoted, but it's in the question which is a reasonable question. Also, you mention that you found (and fixed?) a segfault, but is this the old code or the fixed code? Anyway, shouldn't be in the Q even if it was ok to copy/paste – Peter Cordes Oct 15 '21 at 10:43

5 Answers5

57
FILE *fp;
long lSize;
char *buffer;

fp = fopen ( "blah.txt" , "rb" );
if( !fp ) perror("blah.txt"),exit(1);

fseek( fp , 0L , SEEK_END);
lSize = ftell( fp );
rewind( fp );

/* allocate memory for entire content */
buffer = calloc( 1, lSize+1 );
if( !buffer ) fclose(fp),fputs("memory alloc fails",stderr),exit(1);

/* copy the file into the buffer */
if( 1!=fread( buffer , lSize, 1 , fp) )
  fclose(fp),free(buffer),fputs("entire read fails",stderr),exit(1);

/* do your work here, buffer is a string contains the whole text */

fclose(fp);
free(buffer);
user411313
  • 3,930
  • 19
  • 16
  • 22
    You can close the file before working on the data, rather than after. – R.. GitHub STOP HELPING ICE Sep 19 '10 at 19:40
  • 2
    Any particular reason for calloc over malloc? – Tanaki Sep 30 '13 at 16:22
  • 3
    @Tanaki I usually calloc C strings as a redundant safety mechanism, just in case the C string that is put into the buffer isn't NUL-terminated for some reason. It's probably an unnecessary precaution in most standard cases though. – Ephemera Oct 03 '13 at 03:34
  • 5
    @ephemera: `fread` deals with raw data and won't bother inserting a null-terminator. Using `calloc` also forces your code to iterate over the buffer one more time than is necessary. – diapir Nov 23 '13 at 03:49
13

A solution in the form of a complete program that answers the question and demonstrates it. It is a bit more explicit than other answers and, therefore, easier to understand for those less experienced in C (IMHO).

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

/*
 * 'slurp' reads the file identified by 'path' into a character buffer
 * pointed at by 'buf', optionally adding a terminating NUL if
 * 'add_nul' is true. On success, the size of the file is returned; on
 * failure, -1 is returned and ERRNO is set by the underlying system
 * or library call that failed.
 *
 * WARNING: 'slurp' malloc()s memory to '*buf' which must be freed by
 * the caller.
 */
long slurp(char const* path, char **buf, bool add_nul)
{
    FILE  *fp;
    size_t fsz;
    long   off_end;
    int    rc;

    /* Open the file */
    fp = fopen(path, "rb");
    if( NULL == fp ) {
        return -1L;
    }

    /* Seek to the end of the file */
    rc = fseek(fp, 0L, SEEK_END);
    if( 0 != rc ) {
        return -1L;
    }

    /* Byte offset to the end of the file (size) */
    if( 0 > (off_end = ftell(fp)) ) {
        return -1L;
    }
    fsz = (size_t)off_end;

    /* Allocate a buffer to hold the whole file */
    *buf = malloc( fsz+(int)add_nul );
    if( NULL == *buf ) {
        return -1L;
    }

    /* Rewind file pointer to start of file */
    rewind(fp);

    /* Slurp file into buffer */
    if( fsz != fread(*buf, 1, fsz, fp) ) {
        free(*buf);
        return -1L;
    }

    /* Close the file */
    if( EOF == fclose(fp) ) {
        free(*buf);
        return -1L;
    }

    if( add_nul ) {
        /* Make sure the buffer is NUL-terminated, just in case */
        buf[fsz] = '\0';
    }

    /* Return the file size */
    return (long)fsz;
}


/*
 * Usage message for demo (in main(), below)
 */
void usage(void) {
    fputs("USAGE: ./slurp <filename>\n", stderr);
    exit(1);
}


/*
 * Demonstrates a call to 'slurp'.
 */
int main(int argc, char *argv[]) {
    long  file_size;
    char *buf;

    /* Make sure there is at least one command-line argument */
    if( argc < 2 ) {
        usage();
    }

    /* Try the first command-line argument as a file name */
    file_size = slurp(argv[1], &buf, false);

    /* Bail if we get a negative file size back from slurp() */
    if( file_size < 0L ) {
        perror("File read failed");
        usage();
    }

    /* Write to stdout whatever slurp() read in */
    (void)fwrite(buf, 1, file_size, stdout);

    /* Remember to free() memory allocated by slurp() */
    free( buf );
    return 0;
}
Emmet
  • 6,192
  • 26
  • 39
  • 1
    On WIndows at least you will need to open the file in "rb" mode or fread will return the wrong number. And I got an AccessViolation when add_nul was true. I think I fixed it by using this: `(*buf)[fsz] = '\0';` – Ray Hulha Sep 11 '15 at 13:38
  • @RayHulha: fair point. I haven't used Windows in years and tend to forget about it distinguishing between binary and text mode. You're also right on the 2nd point, there was an extraneous dereference (surplus “*”) in the original. – Emmet Apr 13 '16 at 05:33
  • @Shark: Yes, it works. I can't claim it was extensively tested, but it compiles without warnings under `gcc -std=c99 -pedantic -Wall -Wextra`. I just incorporated two observations by @RayHulha, but a straight copy-paste and compile worked previously. It was never really intended to be a library function, just a demonstration. I changed it so that it accepts a filename on the command-line, rather than always reading from a file called `foo.txt`, which might be more like what people expect from a complete program. – Emmet Apr 13 '16 at 05:38
  • I mean nothing offensive, but it actually does not. It **sometimes** invokes undefined behaviour and leaks heavily. I leaked more than 3.5GB of memory using this... I pasted my workaround. It really *did* work fine for the first couple of tries, but as you said it's nowhere near production-ready. But hey, it's nice and did it's trick for prototyping. Should work for homework(s) too :) – Shark Apr 13 '16 at 10:00
  • Where is it leaking memory ? – Ray Hulha Apr 13 '16 at 13:20
  • It compiles and runs without warning or error under `gcc`. I can't vouch for it under Windows, but I've copy-pasted it from above, compiled, and run it on Linux. The only way it can leak memory is if the caller doesn't observe the memory-managment contract that it must `free()` the space `malloc()`ed by `slurp()`. Even in this little example, the necessity of doing this is clearly documented. – Emmet Apr 13 '16 at 15:19
5

Since I used slurp() expecting it to work, a few days later I found out that.... it doesn't.

So for people that are eager to copy/paste a solution to "getting the contents of a FILE into a char*", here's something you can use.

char* load_file(char const* path)
{
    char* buffer = 0;
    long length;
    FILE * f = fopen (path, "rb"); //was "rb"

    if (f)
    {
      fseek (f, 0, SEEK_END);
      length = ftell (f);
      fseek (f, 0, SEEK_SET);
      buffer = (char*)malloc ((length+1)*sizeof(char));
      if (buffer)
      {
        fread (buffer, sizeof(char), length, f);
      }
      fclose (f);
    }
    buffer[length] = '\0';
    // for (int i = 0; i < length; i++) {
    //     printf("buffer[%d] == %c\n", i, buffer[i]);
    // }
    //printf("buffer = %s\n", buffer);

    return buffer;
}
Shark
  • 6,513
  • 3
  • 28
  • 50
  • 1
    Remember kids, `buffer` HAS to be freed by the caller. – Shark Apr 13 '16 at 10:00
  • 1
    Edit must be at least 6 characters so couldn't fix it. Small bug fix: `buffer[length+1] = '\0';` should be: `buffer[length] = '\0';` – Jos Oct 26 '16 at 13:49
  • `length` should be initialized to `0` at the beginning, in case `if (f)` fails. – vgru Apr 12 '17 at 13:36
  • 1
    If `f` is NULL, access violation happens when writing to `buffer`. Also return value of `fread()` is not checked for errors. – VLL Sep 10 '17 at 09:04
  • 2
    Contrary to the description, people should definitely **not** copy/paste this into a serious program. It's absolutely riddled with bugs. – Craig Barnes Apr 30 '18 at 21:39
  • 1
    I have to agree with @CraigBarnes - this is not production-ready code and should probably **not** be used for anything more serious than homework. – Shark May 03 '18 at 13:06
4

fgets() is a C function that can be used to accomplish this.

Edit: You can also consider using fread().

Shamim Hafiz - MSFT
  • 21,454
  • 43
  • 116
  • 176
  • 2
    on windows you might want to open in binary mode so it doesn't translate cr – Martin Beckett Sep 19 '10 at 19:16
  • No it doesn't. It reads upto newline or end of file. However, the newline read is preserved. Therefore, you could append the read characters directly to the char array and newlines will appear in same manner as the file. – Shamim Hafiz - MSFT Sep 19 '10 at 19:23
  • 1
    Using `fgets` for this purpose makes no sense. It'll be a lot more complicated than a single `fread` and more errorprone. Consider the extra work you'd have to do to handle embedded NUL bytes, for instance.. – R.. GitHub STOP HELPING ICE Sep 19 '10 at 19:39
  • @MartinBeckett OMFSM thank you! Random chars at the end of my string, 2 hours smashing my head against a wall. Also had to add content[size] = '\0'; at the end, not sure if that's Windows specific or if I'm doing something else wrong. –  Jan 17 '14 at 19:58
0

I have used following code to read xml file in to a char buffer and I had to add \0 at the end of file

FILE *fptr;
char *msg;
long length;
size_t read_s = 0;  
fptr = fopen("example_test.xml", "rb");
fseek(fptr, 0L, SEEK_END);
length = ftell(fptr);
rewind(fptr);
msg = (char*)malloc((length+1));
read_s = fread(msg, 1, length, fptr);
*(mip_msg+ read_s) = 0;
if (fptr) fclose(fptr);
sreaga
  • 1
  • 1