I am working on a program that reads text, line by line from input file. Once the line is read, the program reverses order of words in that string, prints it to the output file and starts reading next line. My program reads only specific number of characters from one line, meaning that if line contains more characters then that specific number, all of them have to skipped until next line is reached. My program seems to work fine.
One of the task requirements is to use dynamically allocated arrays. That is the part where my main problem lies. Once I try to free heap-allocated memory, the program fails with error message that says: HEAP CORRUPTION DETECTED. It must be that I messed up something while working with them. However, I am unable to find the real reason.
#include <stdio.h>
#include <stdlib.h>
#define BUFFER_SIZE 255
int readLine(FILE** stream, char** buffer, int* bufferSize);
void reverseString(char* buffer, char** reverse, int bufferSize, int lastLine);
int main(int argc, char** argv)
{
char* buffer = NULL;
char* reverse = NULL;
int bufferSize = 0;
int lastLine = 0;
FILE* intputStream = fopen(argv[1], "r");
FILE* outputStream = fopen(argv[2], "w");
if (intputStream == NULL || outputStream == NULL)
{
printf("Input or output file cannot be opened\n");
return 0;
}
while (!feof(intputStream))
{
lastLine = readLine(&intputStream, &buffer, &bufferSize);
reverse = (char*)malloc(sizeof(char) * bufferSize);
if (reverse != NULL)
{
reverseString(buffer, &reverse, bufferSize, lastLine);
fputs(reverse, outputStream);
}
}
fclose(intputStream);
fclose(outputStream);
free(buffer);
free(reverse);
return 0;
}
int readLine(FILE** stream, char** buffer, int* bufferSize)
{
char tempBuffer[BUFFER_SIZE] = { 0 };
int lastLine = 0;
if (*stream != NULL)
{
fgets(tempBuffer, BUFFER_SIZE, *stream);
char ignoredChar[100] = { 0 };
*bufferSize = strlen(tempBuffer);
// Ignoring in the same line left characters and checking if this is the last line
if (tempBuffer[(*bufferSize) - 1] != '\n')
{
fgets(ignoredChar, 100, *stream);
if (!feof(*stream))
lastLine = 1;
}
// Allocating memory and copying line to dynamically-allocated array
*buffer = (char*)malloc(sizeof(char) * (*bufferSize));
if (*buffer != NULL)
{
memcpy(*buffer, tempBuffer, (*bufferSize));
(*buffer)[(*bufferSize)] = '\0';
}
}
// Return whether or not the last line is read
return lastLine;
}
void reverseString(char* buffer, char** reverse, int bufferSize, int lastLine)
{
int startingValue = (lastLine ? bufferSize - 1 : bufferSize - 2);
int wordStart = startingValue, wordEnd = startingValue;
int index = 0;
while (wordStart > 0)
{
if (buffer[wordStart] == ' ')
{
int i = wordStart + 1;
while (i <= wordEnd)
(*reverse)[index++] = buffer[i++];
(*reverse)[index++] = ' ';
wordEnd = wordStart - 1;
}
wordStart--;
}
for (int i = 0; i <= wordEnd; i++)
{
(*reverse)[index] = buffer[i];
index++;
}
if (!lastLine)
(*reverse)[index++] = '\n';
(*reverse)[index] = '\0';
}
One of the problems is in readLine where you allocate and copy your string like this (code shortened to the relevant parts):
*bufferSize = strlen(tempBuffer);
*buffer = (char*)malloc(sizeof(char) * (*bufferSize));
(*buffer)[(*bufferSize)] = '\0';
This will not allocate space for the null-terminator. And you will write the null-terminator out of bounds of the allocated memory. That leads to undefined behavior.
You need to allocate an extra byte for the null-terminator:
*buffer = malloc(*bufferSize + 1); // +1 for null-terminator
[Note that I don't cast the result, and don't use sizeof(char) because it's specified to always be equal to 1.]
Another problem is because you don't include the null-terminator in the bufferSize the allocation for reverse in main will be wrong as well:
reverse = (char*)malloc(sizeof(char) * bufferSize);
Which should of course be changed to:
reverse = malloc(bufferSize + 1); // +1 for null-terminator
Related
I'm using the getline() function to get every line of stdin. Every line is a string with different length:
#include <stdio.h>
#include <stdlib.h>
int main() {
char *line = NULL;
size_t foo = 0;
ssize_t reader;
while ((reader = getline(&line, &foo, stdin)) != -1) { // %zu of reader is length of line
printf("%s", line);
}
free(line);
return 0;
}
In every iteration, line is a string and is containing the current line. How can I take each string-line and store it inside an array? There are several things I have tried but none of them worked or they just lead to memory access failure :(
I hope my question is clear? If it's not, please tell me and I will change it!
Unless you know up front how many lines to expect, then you will have to allocate the array dynamically, eg:
#include <stdio.h>
#include <stdlib.h>
int main() {
char *line = NULL;
size_t foo = 0;
ssize_t reader;
int result = 0;
int numlines = 0, maxlines = 10;
char **lines = malloc(sizeof(char*) * maxlines);
if (!lines) {
printf("error allocating array\n");
}
else {
while ((reader = getline(&line, &foo, stdin)) != -1) { // %zu of reader is length of line
printf("%s", line);
if (numlines == maxlines) {
maxlines *= 2; // <-- or use whatever threshold makes sense for you
char **newlines = realloc(lines, sizeof(char*) * maxlines);
if (!newlines) {
printf("error reallocating array\n");
result = -1;
break;
}
lines = newlines;
}
lines[numlines] = line;
++numlines;
line = NULL;
foo = 0;
}
free(line); // <-- in case getline() or realloc() failed...
// use lines up to numlines as needed
// free lines
for(int i = 0; i < numlines; ++i) {
free(lines[i]);
}
free(lines);
}
return result;
}
You need to create an array of pointers that gets resized when needed:
#include <stdio.h>
#include <stdlib.h>
int main()
{
// start with an array that ends with a NULL pointer
// (like argv does)
size_t numLines = 0;
char **lines = malloc( ( numLines + 1 ) * sizeof( *lines ) );
lines[ numLines ] = NULL;
// break the loop explicitly - easier to handle and much less
// bug-prone than putting the assignment into a while statement
for ( ;; )
{
// get the next line
size_t bytes = 0UL;
char *line = NULL;
ssize_t result = getline( &line, &bytes, stdin );
if ( result < 0 )
{
break;
}
// enlarge the array by one
numLines++;
char **tmp = realloc( lines, ( numLines + 1 ) * sizeof( *tmp ) );
if ( !tmp )
{
break;
}
lines = tmp;
// add the new line to the end of the array
lines[ numLines ] = line;
lines[ numLines + 1 ] = NULL;
}
// use lines - then free them
return( 0 );
}
That can be optimized by doing the realloc() calls in chunks, such as every 32 or 64 lines. But given that you're already effectively calling malloc() once per line, that might not help much.
So I am writing a little function to parse paths, it looks like this:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int parse_path() {
char *pathname = "this/is/a/path/hello";
int char_index = 0;
char current_char = pathname[char_index];
char *buffer = malloc(2 * sizeof(char));
char *current_char_str = malloc(2 * sizeof(char));
while (current_char != '\0' && (int)current_char != 11) {
if (char_index == 0 && current_char == '/') {
char_index++; current_char = pathname[char_index];
continue;
}
while (current_char != '/' && current_char != '\0') {
current_char_str[0] = current_char;
current_char_str[1] = '\0';
buffer = (char *)realloc(buffer, (strlen(buffer) + 2) * sizeof(char));
strcat(buffer, current_char_str);
char_index++; current_char = pathname[char_index];
}
if (strlen(buffer)) {
printf("buffer(%s)\n", buffer);
current_char_str[0] = '\0';
buffer[0] = '\0';
}
char_index++; current_char = pathname[char_index];
}
};
int main(int argc, char *argv[]) {
parse_path();
printf("hello\n");
return 0;
}
Now, there is undefined behavior in my code, it looks like the printf call inside the main method is changing the buffer variable... as you can see, the output of this program is:
buffer(this)
buffer(is)
buffer(a)
buffer(path)
buffer(hello)
buffer(buffer(%s)
)
buffer(hello)
hello
I have looked at other posts where the same sort of problem is mentioned and people have told me to use a static char array etc. but that does not seem to help.
Any suggestions?
For some reason, at one time in this program the "hello" string from printf is present in my buffer variable.
Sebastian, if you are still having problems after #PaulOgilvie answer, then it is most likely due to not understanding his answer. Your problem is due to buffer being allocated but not initialized. When you call malloc, it allocates a block of at least the size requested, and returns a pointer to the beginning address for the new block -- but does nothing with the contents of the new block -- meaning the block is full random values that just happened to be in the range of addresses for the new block.
So when you call strcat(buffer, current_char_str); the first time and there is nothing but random garbage in buffer and no nul-terminating character -- you do invoke Undefined Behavior. (there is no end-of-string in buffer to be found)
To fix the error, you simply need to make buffer an empty-string after it is allocated by setting the first character to the nul-terminating character, or use calloc instead to allocate the block which will ensure all bytes are set to zero.
For example:
int parse_path (const char *pathname)
{
int char_index = 0, ccs_index = 0;
char current_char = pathname[char_index];
char *buffer = NULL;
char *current_char_str = NULL;
if (!(buffer = malloc (2))) {
perror ("malloc-buffer");
return 0;
}
*buffer = 0; /* make buffer empty-string, or use calloc */
...
Also do not hardcode paths or numbers (that includes the 0 and 2, but we will let those slide for now). Hardcoding "this/is/a/path/hello" within parse_path() make is a rather un-useful function. Instead, make your pathname variable your parameter so I can take any path you want to send to it...
While the whole idea of realloc'ing 2-characters at a time is rather inefficient, you always need to realloc with a temporary pointer rather than the pointer itself. Why? realloc can and does fail and when it does, it returns NULL. If you are using the pointer itself, you will overwrite your current pointer address with NULL in the event of failure, losing the address to your existing block of memory forever creating a memory leak. Instead,
void *tmp = realloc (buffer, strlen(buffer) + 2);
if (!tmp) {
perror ("realloc-tmp");
goto alldone; /* use goto to break nested loops */
}
...
}
alldone:;
/* return something meaningful, your function is type 'int' */
}
A short example incorporating the fixes and temporary pointer would be:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int parse_path (const char *pathname)
{
int char_index = 0, ccs_index = 0;
char current_char = pathname[char_index];
char *buffer = NULL;
char *current_char_str = NULL;
if (!(buffer = malloc (2))) {
perror ("malloc-buffer");
return 0;
}
*buffer = 0; /* make buffer empty-string, or use calloc */
if (!(current_char_str = malloc (2))) {
perror ("malloc-current_char_str");
return 0;
}
while (current_char != '\0' && (int) current_char != 11) {
if (char_index == 0 && current_char == '/') {
char_index++;
current_char = pathname[char_index];
continue;
}
while (current_char != '/' && current_char != '\0') {
current_char_str[0] = current_char;
current_char_str[1] = '\0';
void *tmp = realloc (buffer, strlen(buffer) + 2);
if (!tmp) {
perror ("realloc-tmp");
goto alldone;
}
strcat(buffer, current_char_str);
char_index++;
current_char = pathname[char_index];
}
if (strlen(buffer)) {
printf("buffer(%s)\n", buffer);
current_char_str[0] = '\0';
buffer[0] = '\0';
}
if (current_char != '\0') {
char_index++;
current_char = pathname[char_index];
}
}
alldone:;
return ccs_index;
}
int main(int argc, char* argv[]) {
parse_path ("this/is/a/path/hello");
printf ("hello\n");
return 0;
}
(note: your logic is quite tortured above and you could just use a fixed buffer of PATH_MAX size (include limits.h) and dispense with allocating. Otherwise, you should allocate some anticipated number of characters for buffer to begin with, like strlen (pathname) which would ensure sufficient space for each path component without reallocating. I'd rather over-allocate by 1000-characters than screw up indexing worrying about reallocating 2-characters at a time...)
Example Use/Output
> bin\parsepath.exe
buffer(this)
buffer(is)
buffer(a)
buffer(path)
buffer(hello)
hello
A More Straight-Forward Approach Without Allocation
Simply using a buffer of PATH_MAX size or an allocated buffer of at least strlen (pathname) size will allow you to simply step down your string without any reallocations, e.g.
#include <stdio.h>
#include <limits.h> /* for PATH_MAX - but VS doesn't provide it, so we check */
#ifndef PATH_MAX
#define PATH_MAX 2048
#endif
void parse_path (const char *pathname)
{
const char *p = pathname;
char buffer[PATH_MAX], *b = buffer;
while (*p) {
if (*p == '/') {
if (p != pathname) {
*b = 0;
printf ("buffer (%s)\n", buffer);
b = buffer;
}
}
else
*b++ = *p;
p++;
}
if (b != buffer) {
*b = 0;
printf ("buffer (%s)\n", buffer);
}
}
int main (int argc, char* argv[]) {
char *path = argc > 1 ? argv[1] : "this/is/a/path/hello";
parse_path (path);
printf ("hello\n");
return 0;
}
Example Use/Output
> parsepath2.exe
buffer (this)
buffer (is)
buffer (a)
buffer (path)
buffer (hello)
hello
Or
> parsepath2.exe another/path/that/ends/in/a/filename
buffer (another)
buffer (path)
buffer (that)
buffer (ends)
buffer (in)
buffer (a)
buffer (filename)
hello
Now you can pass any path you would like to parse as an argument to your program and it will be parsed without having to change or recompile anything. Look things over and let me know if you have questions.
You strcat something to buffer but buffer has never been initialized. strcat will first search for the first null character and then copy the string to concatenate there. You are now probably overwriting memory that is not yours.
Before the outer while loop do:
*buffer= '\0';
There are 2 main problems in your code:
the arrays allocated by malloc() are not initialized, so you have undefined behavior when you call strlen(buffer) before setting a null terminator inside the array buffer points to. The program could just crash, but in your case whatever contents is present in the memory block and after it is retained up to the first null byte.
just before the end of the outer loop, you should only take the next character from the path if the current character is a '/'. In your case, you skip the null terminator and the program has undefined behavior as you read beyond the end of the string constant. Indeed, the parse continues through another string constant "buffer(%s)\n" and through yet another one "hello". The string constants seem to be adjacent without padding on your system, which is just a coincidence.
Here is a corrected version:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void parse_path(const char *pathname) {
int char_index = 0;
char current_char = pathname[char_index];
char *buffer = calloc(1, 1);
char *current_char_str = calloc(1, 1);
while (current_char != '\0' && current_char != 11) {
if (char_index == 0 && current_char == '/') {
char_index++; current_char = pathname[char_index];
continue;
}
while (current_char != '/' && current_char != '\0') {
current_char_str[0] = current_char;
current_char_str[1] = '\0';
buffer = (char *)realloc(buffer, strlen(buffer) + 2);
strcat(buffer, current_char_str);
char_index++; current_char = pathname[char_index];
}
if (strlen(buffer)) {
printf("buffer(%s)\n", buffer);
current_char_str[0] = '\0';
buffer[0] = '\0';
}
if (current_char == '/') {
char_index++; current_char = pathname[char_index];
}
}
}
int main(int argc, char *argv[]) {
parse_path("this/is/a/path/hello");
printf("hello\n");
return 0;
}
Output:
buffer(this)
buffer(is)
buffer(a)
buffer(path)
buffer(hello)
hello
Note however some remaining problems:
allocation failure is not tested, resulting in undefined behavior,
allocated blocks are not freed, resulting in memory leaks,
it is unclear why you test current_char != 11: did you mean to stop at TAB or newline?
Here is a much simpler version with the same behavior:
#include <stdio.h>
#include <string.h>
void parse_path(const char *pathname) {
int i, n;
for (i = 0; pathname[i] != '\0'; i += n) {
if (pathname[i] == '/') {
n = 1; /* skip path separators and empty items */
} else {
n = strcspn(pathname + i, "/"); /* get the length of the path item */
printf("buffer(%.*s)\n", n, pathname + i);
}
}
}
int main(int argc, char *argv[]) {
parse_path("this/is/a/path/hello");
printf("hello\n");
return 0;
}
I'd like to read a big file while the first character of a line isn't " ".
But the code I have written is very slow. How can I speed up the routine?
Is there a better solution instead of getline?
void readString(const char *fn)
{
FILE *fp;
char *vString;
struct stat fdstat;
int stat_res;
stat_res = stat(fn, &fdstat);
fp = fopen(fn, "r+b");
if (fp && !stat_res)
{
vString = (char *)calloc(fdstat.st_size + 1, sizeof(char));
int dataEnd = 1;
size_t len = 0;
int emptyLine = 1;
char **linePtr = malloc(sizeof(char*));
*linePtr = NULL;
while(dataEnd)
{
// Check every line
getline(linePtr, &len, fp);
// When data ends, the line begins with space (" ")
if(*linePtr[0] == 0x20)
emptyLine = 0;
// If line begins with space, stop writing
if(emptyLine)
strcat(vString, *linePtr);
else
dataEnd = 0;
}
strcat(vString, "\0");
free(linePtr);
linePtr = NULL;
}
}
int main(int argc, char **argv){
readString(argv[1]);
return EXIT_SUCCESS;
}
How can I speed up the routine?
The most suspicious aspect of your program performance-wise is the strcat(). On each call, it needs to scan the whole destination string from the beginning to find the place to append the source string. As a result, if your file's lines have length bounded by a constant (even a large one), then your approach's performance scales with the square of the file length.
The asymptotic complexity analysis doesn't necessarily tell the whole story, though. The I/O part of your code scales linearly with file length, and since I/O is much more expensive than in-memory data manipulation, that will dominate your performance for small enough files. If you're in that regime then you're probably not going to do much better than you already do. In that event, though, you might still do a bit better by reading the whole file at once via fread(), and then scanning it for end-of-data via strstr():
size_t nread = fread(vString, 1, fdstat.st_size, fp);
// Handle nread != fdstat.st_size ...
// terminate the buffer as a string
vString[nread] = '\0';
// truncate the string after the end-of-data:
char *eod = strstr(vString, "\n ");
if (eod) {
// terminator found - truncate the string after the newline
eod[1] = '\0';
} // else no terminator found
That scales linearly, so it addresses your asymptotic complexity problem, too, but if the data of interest will often be much shorter than the file, then it will leave you in those cases doing a lot more costly I/O than you need to do. In that event, one alternative would be to read in chunks, as #laissez_faire suggested. Another would be to tweak your original algorithm to track the end of vString so as to use strcpy() instead of strcat() to append each new line. The key part of that version would look something like this:
char *linePtr = NULL;
size_t nread = 0;
size_t len = 0;
*vString = '\0'; // In case the first line is end-of-data
for (char *end = vString; ; end += nread) {
// Check every line
nread = getline(&linePtr, &len, fp);
if (nread < 0) {
// handle eof or error ...
}
// When data ends, the line begins with space (" ")
if (*linePtr == ' ') {
break;
}
strcpy(end, *linePtr);
}
free(linePtr);
Additionally, note that
you do not need to initially zero-fill the memory allocated for *vString, as you're just going to overwrite those zeroes with the data of real interest (and then ignore the rest of the buffer).
You should not cast the return value of malloc()-family functions, including calloc().
Have you tried to read the file using fread and read a bigger chunk of data in each step and then parse the data after reading it? Something like:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
char *readString(const char *fn)
{
FILE *fp;
char *vString;
struct stat fdstat;
int stat_res;
stat_res = stat(fn, &fdstat);
fp = fopen(fn, "r+b");
if (fp && !stat_res) {
vString = (char *) calloc(fdstat.st_size + 1, sizeof(char));
int newline = 1;
int index = 0;
while (index < fdstat.st_size) {
int len =
fdstat.st_size - index >
4096 ? 4096 : fdstat.st_size - index;
char *buffer = (char *) malloc(len);
int read_len = fread(buffer, 1, len, fp);
int i;
if (newline) {
if (read_len > 0 && buffer[0] == ' ') {
return vString;
}
newline = 0;
}
for (i = 0; i < read_len; ++i) {
if (buffer[i] == '\n') {
if (i + 1 < read_len && buffer[i + 1] == ' ') {
memcpy(vString + index, buffer, i + 1);
return vString;
}
newline = 1;
}
}
memcpy(vString + index, buffer, read_len);
index += read_len;
}
}
return vString;
}
int main(int argc, char **argv)
{
char *str = readString(argv[1]);
printf("%s", str);
free(str);
return EXIT_SUCCESS;
}
I am given a text file and I need to put it in a buffer and use get_lines to make an array of pointers after converting each line to a string. I am having trouble with just the get_lines function as I am getting a seg fault when run it.
Here's my code:
#include <stdlib.h>
#include <stdio.h>
int readfile(FILE *fp, char **cbuf);
char **get_lines(char *cbuf, int bufsize, int word);
int readword(FILE*tmp);
int main(int argc, char *argv[])
{
int i,bufsize, num_word;
char *cbuf;
char **lines;
FILE *fp;
FILE *tmp;
if( (fp=fopen(argv[1],"r")) == NULL)
{
perror("ERROR: bad/no filename");
exit(0);
}
tmp = fopen(argv[1],"r");
bufsize = readfile(fp,&cbuf);
num_word = readword(tmp);
lines = get_lines(cbuf, bufsize, num_word) ;
i=0;
while( lines[i] != NULL) {
printf("%i\t%s\n",i,lines[i]);
i++;
}
return 0;
}
int readfile(FILE *fp,char**cbuf)
{
int i;
char c;
fseek(fp, 0L, SEEK_END);
int bufsize = ftell(fp);
fseek(fp, 0L, SEEK_SET);
*cbuf = (char *)malloc(sizeof(char) * bufsize);
for (i = 0; i < bufsize; i++)
{
c = fgetc(fp);
(*cbuf)[i] = c;
}
return bufsize;
}
int readword(FILE*tmp)
{
int word = 0;
char c;
while((c = fgetc(tmp)) != EOF )
{
if (c == '\n')
word++;
}
return word;
}
char **get_lines(char *cbuf, int bufsize, int word)
{
int i = 0, j = 0, counter = 0;
char (*lines)[bufsize];
lines = (char**)malloc(sizeof(char*)*bufsize);
counter = cbuf;
for (i = 0; i < bufsize; i++)
{
if(cbuf[i] == '\n')
{
cbuf[i] == '\0';
counter = cbuf[i + 1];
j++;
}else
{
*lines[j] = &counter;
}
}
lines[word] == NULL;
return lines;
}
The violation causing the fault is not immediately obvious to me, can someone tell me what might be wrong in get_lines()?
This code is wrong:
char (*lines)[bufsize];
lines = (char**)malloc(sizeof(char*)*bufsize);
It allocates a pointer to an array of char. Then you malloc the wrong amount of space, cast to the wrong type, and write *lines[j] = &counter; which tries to store a pointer in a char.
You should get many compiler errors/warnings for the get_lines function. It's important to pay attention to such messages as they are telling you that something is wrong with your code. There's no point even starting to investigate a segfault until you have fixed all the errors and warnings.
See here for a great guide on how to debug your code; I suspect you would fail the rubber duckie test on the get_lines function.
Here is a fixed version (untested):
// Precondition: cbuf[bufsize] == '\0'
//
char **get_lines(char *cbuf, size_t bufsize, size_t num_lines)
{
// +1 for the NULL termination of the list
char **lines = malloc((num_lines + 1) * sizeof *lines);
size_t line = 0;
while ( line < num_lines )
{
lines[line++] = cbuf;
cbuf = strchr(cbuf, '\n');
if ( !cbuf )
break;
*cbuf++ = '\0';
}
lines[line] = NULL;
return lines;
}
In your existing code there is no room to write the null terminator for the last line; my advice is to make readfile actually malloc one extra byte and make sure that is set to 0.
I need a version of read line that is memory save. I have this "working" solution. But I'm not sure how it behaves with memory. When I enable free(text) it works for a few lines and then I get an error. So now neither text nor result is ever freed although I malloc text. Is that correct ? And why is that so ?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char* readFromIn()
{
char* text = malloc(1024);
char* result = fgets(text, 1024, stdin);
if (result[strlen(result) - 1] == 10)
result[strlen(result) - 1] = 0;
//free(text);
return result;
}
I have A LOT of short lines to read with this and I also need stdin to be replaceable with a FILE* handle. There is no need for me to realloc text because I have only short lines.
fgets returns a pointer to the string, so after the fgets line, result will be the same memory address as text. Then when you call free (text); you are returning invalid memory.
You should free the memory in the calling function when you have finished with result
You could also avoid the malloc/free stuff by structuring your code to pass a buffer something like this:
void parent_function ()
{
char *buffer[1024];
while (readFromIn(buffer)) {
// Process the contents of buffer
}
}
char *readFromIn(char *buffer)
{
char *result = fgets(buffer, 1024, stdin);
int len;
// fgets returns NULL on error of end of input,
// in which case buffer contents will be undefined
if (result == NULL) {
return NULL;
}
len = strlen (buffer);
if (len == 0) {
return NULL;
}
if (buffer[len - 1] == '\n') {
buffer[len - 1] = 0;
return buffer;
}
Trying to avoid the malloc/free is probably wise if you are dealing with many small, short lived items so that the memory doesn't get fragmented and it should faster as well.
char *fgets(char *s, int size, FILE *stream) reads in at most one less than size characters from stream and stores them into the buffer pointed to by s. Reading stops after an EOF or a newline. If a newline is read, it is stored into the buffer. A terminating null byte ('\0') is stored after the last character in the buffer.
Return Value: returns s on success, and NULL on error or when end of file occurs while no characters have been read.
So there are 2 critical problems with your code:
You don't check the return value of fgets
You want to deallocate the memory, where this string is stored and return a pointer to this memory. Accessing the memory, where such a pointer (dangling pointer) points to, leads to undefined behaviour.
Your function could look like this:
public char* readFromIn() {
char* text = malloc(1024);
if (fgets(text, 1024, stdin) != NULL) {
int textLen = strlen(text);
if (textLen > 0 && text[textLen - 1] == '\n')
text[textLen - 1] == '\0'; // getting rid of newline character
return text;
}
else {
free(text);
return NULL;
}
}
and then caller of this function should be responsible for deallocating the memory that return value of this function points to.
I know you mentioned that the lines are only short, but none of the solutions provided will work for lines greater than 1024 in length. It is for this reason that I provide a solution which will attempt to read entire lines, and resize the buffer when there's not enough space.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MINIMUM_CAPACITY 16
size_t read_line(char **buffer, size_t *capacity) {
char *buf = *buffer;
size_t cap = *capacity, pos = 0;
if (cap < MINIMUM_CAPACITY) { cap = MINIMUM_CAPACITY; }
for (;;) {
buf = realloc(buf, cap);
if (buf == NULL) { return pos; }
*buffer = buf;
*capacity = cap;
if (fgets(buf + pos, cap - pos, stdin) == NULL) {
break;
}
pos += strcspn(buf + pos, "\n");
if (buf[pos] == '\n') {
break;
}
cap *= 2;
}
return pos;
}
int main(void) {
char *line = NULL;
size_t size = 0;
for (size_t end = read_line(&line, &size); line[end] == '\n'; end = read_line(&line, &size)) {
line[end] = '\0'; // trim '\n' off the end
// process contents of buffer here
}
free(line);
return 0;
}
An ideal solution should be able to operate with a fixed buffer of 1 byte. This requires a more comprehensive understanding of the problem, however. Once achieved, adapting such a solution would achieve the most optimal solution.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *readFromIn(FILE *fp)
{
char text[1024];
size_t len;
if (!fgets(text, sizeof text, fp)) return NULL;
len = strlen(text);
while (len && text[len-1] == '\n') text[--len] = 0;
return strdup(text);
}
Why did no one propose to move the buffer from heap to stack ? This is my solution now:
char input[1024]; // held ready as buffer for fgets
char* readFromIn()
{
char* result = fgets(input, 1024, stdin);
if (result == null)
return "";
if (result[strlen(result) - 1] == '\n')
result[strlen(result) - 1] = 0;
return result;
}