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.

3
  • 1
    Possible duplicate of Easiest way to get file's contents in C Commented Oct 29, 2015 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 Commented Jan 5, 2017 at 5: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 Commented Oct 15, 2021 at 10:43

5 Answers 5

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);
Sign up to request clarification or add additional context in comments.

4 Comments

You can close the file before working on the data, rather than after.
Any particular reason for calloc over malloc?
@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: 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.
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;
}

6 Comments

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';
@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.
@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.
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 :)
Where is it leaking memory ?
|
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;
}

6 Comments

Remember kids, buffer HAS to be freed by the caller.
Edit must be at least 6 characters so couldn't fix it. Small bug fix: buffer[length+1] = '\0'; should be: buffer[length] = '\0';
If f is NULL, access violation happens when writing to buffer. Also return value of fread() is not checked for errors.
Contrary to the description, people should definitely not copy/paste this into a serious program. It's absolutely riddled with bugs.
I have to agree with @CraigBarnes - this is not production-ready code and should probably not be used for anything more serious than homework.
|
4

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

Edit: You can also consider using fread().

4 Comments

on windows you might want to open in binary mode so it doesn't translate cr
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.
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..
@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.
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);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.