C read file line by line

CFile IoLineLibc

C Problem Overview


I wrote this function to read a line from a file:

const char *readLine(FILE *file) {
	
	if (file == NULL) {
		printf("Error: file pointer is null.");
		exit(1);
	}
	
	int maximumLineLength = 128;
	char *lineBuffer = (char *)malloc(sizeof(char) * maximumLineLength);
	
	if (lineBuffer == NULL) {
		printf("Error allocating memory for line buffer.");
		exit(1);
	}
	
	char ch = getc(file);
	int count = 0;
	
	while ((ch != '\n') && (ch != EOF)) {
		if (count == maximumLineLength) {
			maximumLineLength += 128;
			lineBuffer = realloc(lineBuffer, maximumLineLength);
			if (lineBuffer == NULL) {
				printf("Error reallocating space for line buffer.");
				exit(1);
			}
		}
		lineBuffer[count] = ch;
		count++;
		
		ch = getc(file);
	}
	
	lineBuffer[count] = '\0';
	char line[count + 1];
	strncpy(line, lineBuffer, (count + 1));
	free(lineBuffer);
	const char *constLine = line;
	return constLine;
}

The function reads the file correctly, and using printf I see that the constLine string did get read correctly as well.

However, if I use the function e.g. like this:

while (!feof(myFile)) {
	const char *line = readLine(myFile);
	printf("%s\n", line);
}

printf outputs gibberish. Why?

C Solutions


Solution 1 - C

If your task is not to invent the line-by-line reading function, but just to read the file line-by-line, you may use a typical code snippet involving the getline() function (see the manual page here):

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    FILE * fp;
    char * line = NULL;
    size_t len = 0;
    ssize_t read;

    fp = fopen("/etc/motd", "r");
    if (fp == NULL)
        exit(EXIT_FAILURE);

    while ((read = getline(&line, &len, fp)) != -1) {
        printf("Retrieved line of length %zu:\n", read);
        printf("%s", line);
    }
       
    fclose(fp);
    if (line)
        free(line);
    exit(EXIT_SUCCESS);
}

Solution 2 - C

FILE* filePointer;
int bufferLength = 255;
char buffer[bufferLength]; /* not ISO 90 compatible */

filePointer = fopen("file.txt", "r");

while(fgets(buffer, bufferLength, filePointer)) {
    printf("%s\n", buffer);
}

fclose(filePointer);

Solution 3 - C

In your readLine function, you return a pointer to the line array (Strictly speaking, a pointer to its first character, but the difference is irrelevant here). Since it's an automatic variable (i.e., it's “on the stack”), the memory is reclaimed when the function returns. You see gibberish because printf has put its own stuff on the stack.

You need to return a dynamically allocated buffer from the function. You already have one, it's lineBuffer; all you have to do is truncate it to the desired length.

    lineBuffer[count] = '\0';
    realloc(lineBuffer, count + 1);
    return lineBuffer;
}

ADDED (response to follow-up question in comment): readLine returns a pointer to the characters that make up the line. This pointer is what you need to work with the contents of the line. It's also what you must pass to free when you've finished using the memory taken by these characters. Here's how you might use the readLine function:

char *line = readLine(file);
printf("LOG: read a line: %s\n", line);
if (strchr(line, 'a')) { puts("The line contains an a"); }
/* etc. */
free(line);
/* After this point, the memory allocated for the line has been reclaimed.
   You can't use the value of `line` again (though you can assign a new value
   to the `line` variable if you want). */

Solution 4 - C

//open and get the file handle
FILE* fh;
fopen_s(&fh, filename, "r");

//check if file exists
if (fh == NULL){
	printf("file does not exists %s", filename);
	return 0;
}
  

//read line by line
const size_t line_size = 300;
char* line = malloc(line_size);
while (fgets(line, line_size, fh) != NULL)  {
	printf(line);
}
free(line);    // dont forget to free heap memory
 

Solution 5 - C

A complete, fgets() solution:

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

#define MAX_LEN 256

int main(void)
{
	FILE* fp;
	fp = fopen("file.txt", "r");
	if (fp == NULL) {
      perror("Failed: ");
      return 1;
	}

	char buffer[MAX_LEN];
	while (fgets(buffer, MAX_LEN, fp))
	{
        // Remove trailing newline
		buffer[strcspn(buffer, "\n")] = 0;
        printf("%s\n", buffer);
	}

	fclose(fp);
	return 0;
}

Output:

First line of file
Second line of file
Third (and also last) line of file

Remember, if you want to read from Standard Input (rather than a file as in this case), then all you have to do is pass stdin as the third parameter of fgets() method, like this:

while(fgets(buffer, MAX_LEN, stdin))

Appendix

https://stackoverflow.com/questions/2693776/removing-trailing-newline-character-from-fgets-input

https://stackoverflow.com/questions/14680232/how-to-detect-a-file-is-opened-or-not-in-c

Solution 6 - C

readLine() returns pointer to local variable, which causes undefined behaviour.

To get around you can:

  1. Create variable in caller function and pass its address to readLine()
  2. Allocate memory for line using malloc() - in this case line will be persistent
  3. Use global variable, although it is generally a bad practice

Solution 7 - C

Use fgets() to read a line from a file handle.

Solution 8 - C

Some things wrong with the example:

  • you forgot to add \n to your printfs. Also error messages should go to stderr i.e. fprintf(stderr, ....

  • (not a biggy but) consider using fgetc() rather than getc(). getc() is a macro, fgetc() is a proper function

  • getc() returns an int so ch should be declared as an int. This is important since the comparison with EOF will be handled correctly. Some 8 bit character sets use 0xFF as a valid character (ISO-LATIN-1 would be an example) and EOF which is -1, will be 0xFF if assigned to a char.

  • There is a potential buffer overflow at the line

     lineBuffer[count] = '\0';
    

    If the line is exactly 128 characters long, count is 128 at the point that gets executed.

  • As others have pointed out, line is a locally declared array. You can't return a pointer to it.

  • strncpy(count + 1) will copy at most count + 1 characters but will terminate if it hits '\0' Because you set lineBuffer[count] to '\0' you know it will never get to count + 1. However, if it did, it would not put a terminating '\0' on, so you need to do it. You often see something like the following:

     char buffer [BUFFER_SIZE];
     strncpy(buffer, sourceString, BUFFER_SIZE - 1);
     buffer[BUFFER_SIZE - 1] = '\0';
    
  • if you malloc() a line to return (in place of your local char array), your return type should be char* - drop the const.

Solution 9 - C

Here is my several hours... Reading whole file line by line.

char * readline(FILE *fp, char *buffer)
{
    int ch;
    int i = 0;
    size_t buff_len = 0;

    buffer = malloc(buff_len + 1);
    if (!buffer) return NULL;  // Out of memory

    while ((ch = fgetc(fp)) != '\n' && ch != EOF)
    {
        buff_len++;
        void *tmp = realloc(buffer, buff_len + 1);
        if (tmp == NULL)
        {
            free(buffer);
            return NULL; // Out of memory
        }
        buffer = tmp;

        buffer[i] = (char) ch;
        i++;
    }
    buffer[i] = '\0';

    // Detect end
    if (ch == EOF && (i == 0 || ferror(fp)))
    {
        free(buffer);
        return NULL;
    }
    return buffer;
}

void lineByline(FILE * file){
char *s;
while ((s = readline(file, 0)) != NULL)
{
    puts(s);
    free(s);
    printf("\n");
}
}

int main()
{
    char *fileName = "input-1.txt";
    FILE* file = fopen(fileName, "r");
    lineByline(file);
    return 0;
}

Solution 10 - C

const char *readLine(FILE *file, char* line) {

    if (file == NULL) {
        printf("Error: file pointer is null.");
        exit(1);
    }

    int maximumLineLength = 128;
    char *lineBuffer = (char *)malloc(sizeof(char) * maximumLineLength);

    if (lineBuffer == NULL) {
        printf("Error allocating memory for line buffer.");
        exit(1);
    }

    char ch = getc(file);
    int count = 0;

    while ((ch != '\n') && (ch != EOF)) {
        if (count == maximumLineLength) {
            maximumLineLength += 128;
            lineBuffer = realloc(lineBuffer, maximumLineLength);
            if (lineBuffer == NULL) {
                printf("Error reallocating space for line buffer.");
                exit(1);
            }
        }
        lineBuffer[count] = ch;
        count++;

        ch = getc(file);
    }

    lineBuffer[count] = '\0';
    char line[count + 1];
    strncpy(line, lineBuffer, (count + 1));
    free(lineBuffer);
    return line;

}


char linebuffer[256];
while (!feof(myFile)) {
    const char *line = readLine(myFile, linebuffer);
    printf("%s\n", line);
}

note that the 'line' variable is declared in calling function and then passed, so your readLine function fills predefined buffer and just returns it. This is the way most of C libraries work.

There are other ways, which I'm aware of:

  • defining the char line[] as static (static char line[MAX_LINE_LENGTH] -> it will hold it's value AFTER returning from the function). -> bad, the function is not reentrant, and race condition can occur -> if you call it twice from two threads, it will overwrite it's results
  • malloc()ing the char line[], and freeing it in calling functions -> too many expensive mallocs, and, delegating the responsibility to free the buffer to another function (the most elegant solution is to call malloc and free on any buffers in same function)

btw, 'explicit' casting from char* to const char* is redundant.

btw2, there is no need to malloc() the lineBuffer, just define it char lineBuffer[128], so you don't need to free it

btw3 do not use 'dynamic sized stack arrays' (defining the array as char arrayName[some_nonconstant_variable]), if you don't exactly know what are you doing, it works only in C99.

Solution 11 - C

void readLine(FILE* file, char* line, int limit)
{
    int i;
    int read;

    read = fread(line, sizeof(char), limit, file);
    line[read] = '\0';

    for(i = 0; i <= read;i++)
    {
        if('\0' == line[i] || '\n' == line[i] || '\r' == line[i])
        {
            line[i] = '\0';
            break;
        }
    }

    if(i != read)
    {
        fseek(file, i - read + 1, SEEK_CUR);
    }
}

what about this one?

Solution 12 - C

Implement method to read, and get content from a file (input1.txt)

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

void testGetFile() {
    // open file
    FILE *fp = fopen("input1.txt", "r");
    size_t len = 255;
    // need malloc memory for line, if not, segmentation fault error will occurred.
    char *line = malloc(sizeof(char) * len);
    // check if file exist (and you can open it) or not
    if (fp == NULL) {
        printf("can open file input1.txt!");
        return;
    }
    while(fgets(line, len, fp) != NULL) {
        printf("%s\n", line);
    }
    free(line);
}

Hope this help. Happy coding!

Solution 13 - C

You should use the ANSI functions for reading a line, eg. fgets. After calling you need free() in calling context, eg:

...
const char *entirecontent=readLine(myFile);
puts(entirecontent);
free(entirecontent);
...

const char *readLine(FILE *file)
{
  char *lineBuffer=calloc(1,1), line[128];

  if ( !file || !lineBuffer )
  {
    fprintf(stderr,"an ErrorNo 1: ...");
    exit(1);
  }

  for(; fgets(line,sizeof line,file) ; strcat(lineBuffer,line) )
  {
    if( strchr(line,'\n') ) *strchr(line,'\n')=0;
    lineBuffer=realloc(lineBuffer,strlen(lineBuffer)+strlen(line)+1);
    if( !lineBuffer )
    {
      fprintf(stderr,"an ErrorNo 2: ...");
      exit(2);
    }
  }
  return lineBuffer;
}

Solution 14 - C

My implement from scratch:

FILE *pFile = fopen(your_file_path, "r");
int nbytes = 1024;
char *line = (char *) malloc(nbytes);
char *buf = (char *) malloc(nbytes);

size_t bytes_read;
int linesize = 0;
while (fgets(buf, nbytes, pFile) != NULL) {
    bytes_read = strlen(buf);
	// if line length larger than size of line buffer
    if (linesize + bytes_read > nbytes) {
        char *tmp = line;
        nbytes += nbytes / 2;
        line = (char *) malloc(nbytes);
        memcpy(line, tmp, linesize);
        free(tmp);
    }
    memcpy(line + linesize, buf, bytes_read);
    linesize += bytes_read;
    
    if (feof(pFile) || buf[bytes_read-1] == '\n') {
        handle_line(line);
        linesize = 0;
        memset(line, '\0', nbytes);
    }
}

free(buf);
free(line);

Solution 15 - C

Provide a portable and generic getdelim function, test passed via msvc, clang, gcc.

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

ssize_t
portabl_getdelim(char ** restrict linep,
				 size_t * restrict linecapp,
				 int delimiter,
				 FILE * restrict stream) {
	if (0 == *linep) {
		*linecapp = 8;
		*linep = malloc(*linecapp);
		if (0 == *linep) {
			return EOF;
		}
	}

	ssize_t linelen = 0;
	int c = 0;
	char *p = *linep;
	
	while (EOF != (c = fgetc(stream))) {
		if (linelen == (ssize_t) *linecapp - 1) {
			*linecapp <<= 1;
			char *p1 = realloc(*linep, *linecapp);
			if (0 == *p1) {
				return EOF;
			}
			p = p1 + linelen;
		}
		*p++ = c;
		linelen++;
		
		if (delimiter == c) {
			*p = 0;
			return linelen;
		}
	}
	return EOF == c ? EOF : linelen;
}


int
main(int argc, char **argv) {
 	const char *filename = "/a/b/c.c";
	FILE *file = fopen(filename, "r");
	if (!file) {
		perror(filename);
		return 1;
	}

	char *line = 0;
	size_t linecap = 0;
	ssize_t linelen;
	
	while (0 < (linelen = portabl_getdelim(&line, &linecap, '\n', file))) {
		fwrite(line, linelen, 1, stdout);
	}
	if (line) {
		free(line);
	}
	fclose(file);	

	return 0;
}

Solution 16 - C

You make the mistake of returning a pointer to an automatic variable. The variable line is allocated in the stack and only lives as long as the function lives. You are not allowed to return a pointer to it, because as soon as it returns the memory will be given elsewhere.

const char* func x(){
    char line[100];
    return (const char*) line; //illegal
}

To avoid this, you either return a pointer to memory which resides on the heap eg. lineBuffer and it should be the user's responsibility to call free() when he is done with it. Alternatively you can ask the user to pass you as an argument a memory address on which to write the line contents at.

Solution 17 - C

I want a code from ground 0 so i did this to read the content of dictionary's word line by line.

char temp_str[20]; // you can change the buffer size according to your requirements And A single line's length in a File.

Note I've initialized the buffer With Null character each time I read line.This function can be Automated But Since I need A proof of Concept and want to design a programme Byte By Byte

#include<stdio.h>

int main()
{
int i;
char temp_ch;
FILE *fp=fopen("data.txt","r");
while(temp_ch!=EOF)
{
 i=0;
  char temp_str[20]={'\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0'};
while(temp_ch!='\n')
{
  temp_ch=fgetc(fp);
  temp_str[i]=temp_ch;
  i++;
}
if(temp_ch=='\n')
{
temp_ch=fgetc(fp);
temp_str[i]=temp_ch;
}
printf("%s",temp_str);
}
return 0;
}


              
                   

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionlronView Question on Stackoverflow
Solution 1 - CmbaitoffView Answer on Stackoverflow
Solution 2 - CRobView Answer on Stackoverflow
Solution 3 - CGilles 'SO- stop being evil'View Answer on Stackoverflow
Solution 4 - CRevoLabView Answer on Stackoverflow
Solution 5 - CgsamarasView Answer on Stackoverflow
Solution 6 - CqrdlView Answer on Stackoverflow
Solution 7 - CRaku EscapeView Answer on Stackoverflow
Solution 8 - CJeremyPView Answer on Stackoverflow
Solution 9 - CSamirView Answer on Stackoverflow
Solution 10 - CnothrowView Answer on Stackoverflow
Solution 11 - CTaner MansurView Answer on Stackoverflow
Solution 12 - CNhat DinhView Answer on Stackoverflow
Solution 13 - Cuser411313View Answer on Stackoverflow
Solution 14 - CtjeubaoitView Answer on Stackoverflow
Solution 15 - C南山竹View Answer on Stackoverflow
Solution 16 - CLefteris EView Answer on Stackoverflow
Solution 17 - CMohit DabasView Answer on Stackoverflow