1

I want the following code to accomplish to store the content of the file it reads in a variable content without leaking memory:

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

char* concat(const char *s1, const char *s2)
{
    const size_t len1 = strlen(s1);
    const size_t len2 = strlen(s2);
    char *result = malloc(len1 + len2 + 1); 
    memcpy(result, s1, len1);
    memcpy(result + len1, s2, len2 + 1); 
    return result;
}

int main() {

char path[] = "/home/jim/trackers_best.txt";
FILE *archivo = fopen(path,"r");
char line[200];
char * content = "";


if (archivo == NULL)
        exit(1);
else {

fseek(archivo, 0L, SEEK_END);
  int sz = ftell(archivo);
  fseek(archivo, 0L, SEEK_SET);
  if (sz < pow(10, 7) ) {
        printf("\nThe content of %s is:\n", path);
        while (feof(archivo) == 0) {
        fgets(line, 200, archivo); //reads one line at a time - comment out the while loop to find out
        content = concat(content, line);
       //free(content);//No leaks but no content either       
            }
          }
  else {
      puts("File take up more than 10 MBs, I refuse to read it.");
      fclose(archivo);
      exit(0);
        }
      }
fclose(archivo);
puts(content); 
free(content);
return 0;   
}

much the same as this code that uses fread() does it:

#include<stdio.h>
#include<stdlib.h>
#include <math.h>
#include <sys/types.h> 

int main() {
 
int count;  
char path[] = "/home/jim/trackers_best.txt";
FILE *archivo = fopen(path,"rb");
    
 if (archivo == NULL) {
     puts("Does not exist.");
     exit(1); 
       }
 else {
  
  fseek(archivo, 0L, SEEK_END);
  int sz = ftell(archivo);
  char contenido[sz];
  fseek(archivo, 0L, SEEK_SET);
  if (sz < pow(10, 7) ) {
    count = fread(&contenido, 10 * sizeof(char), sz, archivo);//reads 10 characters at a time.
    }
  else {
      puts("File take up more than 10 MBs, I refuse to read it.");
        }
  fclose(archivo);
  // Printing data to check validity
  printf("Data read from file: %s \n", contenido);
  printf("Elements read: %i, read %li byte at a time.\n", count, 10 * sizeof(char));
  
 }
return 0;   
}

The first code snippet leaks memory, as evidenced by valgrind:

...
total heap usage: 44 allocs, 4 frees, 23,614 bytes allocated
...
==404853== LEAK SUMMARY:
==404853==    definitely lost: 17,198 bytes in 40 blocks
...

When I attempt to prevent such leak by executing free() within the while loop I get no value stored in content. How might I store the string read by fgets without leaking memory?

John Smith
  • 835
  • 1
  • 7
  • 19
  • 2
    Read this: [**Why is “while ( !feof (file) )” always wrong?**](https://stackoverflow.com/questions/5431941/why-is-while-feof-file-always-wrong) – Andrew Henle Feb 12 '22 at 18:22

2 Answers2

2

When you call concat the second time, the value from the first call is overwritten by the malloc call. That is the memory leak.

I presume that you want content to be one long string that contains the data from all lines.

You want to use realloc (vs. malloc) in concat and just append s2 to the enlarged s1.

With this modified scheme, instead of char *content = "";, you want: char *content = NULL;

Also, don't use feof

And, ftell returns long and not int. And, it's better to not use pow to compare against the limit.

Here is the refactored code (I've compiled it but not tested it):

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

char *
concat(char *s1, const char *s2)
{
    size_t len1;
    size_t len2;

    if (s1 != NULL)
        len1 = strlen(s1);
    else
        len1 = 0;

    len2 = strlen(s2);

    s1 = realloc(s1,len1 + len2 + 1);
    if (s1 == NULL) {
        perror("realloc");
        exit(1);
    }

    memcpy(s1 + len1, s2, len2 + 1);

    return s1;
}

int
main(void)
{

    char path[] = "/home/jim/trackers_best.txt";
    FILE *archivo = fopen(path, "r");
    char line[200];
#if 0
    char *content = "";
#else
    char *content = NULL;
#endif

    if (archivo == NULL)
        exit(1);

    fseek(archivo, 0L, SEEK_END);
    long sz = ftell(archivo);
    fseek(archivo, 0L, SEEK_SET);

    if (sz < (10 * 1024 * 1024)) {
        printf("\nThe content of %s is:\n", path);

        while (fgets(line, sizeof(line), archivo) != NULL)
            content = concat(content, line);
    }
    else {
        puts("File take up more than 10 MBs, I refuse to read it.");
        fclose(archivo);
        exit(0);
    }

    fclose(archivo);

    if (content != NULL)
        puts(content);

    free(content);

    return 0;
}
Craig Estey
  • 30,627
  • 4
  • 24
  • 48
  • Thanks. Tested, it works. Why is it better to avoid `pow` when comparing against the limit? I did choose it for the sake of readability, i.e. the alternative notation of `10000000` has so many zeroes that I decided to use `pow(10, 7)` instead. – John Smith Feb 12 '22 at 19:14
  • 1
    `pow` is _not_ precise and it's slow and has to compute the value dynamically. Better to use fixed point math, particularly when dealing with memory sizes/offsets or file sizes/offsets. And, use a _constant_. Also, 1000 is _not_ a kilobyte [as _programmers_ define it]. It is 1024. So, 10 megabytes is: 10,485,760. This is `10 * 1024 * 1024` which will be constant folded by the compiler into a single constant. When dealing with this a lot, one could put the following in a header file (e.g.): `#define KB 1024 #define MB (1024 * 1024) #define GB (1024 * 1024 * 1024)` Then, just do: `10 * MB` – Craig Estey Feb 12 '22 at 19:24
1

You knew what you needed to do but didnt do it right

    content = concat(content, line);
   //free(content);//No leaks but no content either 

you need to keep the old content pointer so you can delete it after you create a new one

    char *oldc = content;
    content = concat(content, line);
    free(oldc);
pm100
  • 48,078
  • 23
  • 82
  • 145
  • This is a problem on the first iteration because `char * content = "";` means `content` won't point to a block allocated by `malloc`. – Retired Ninja Feb 12 '22 at 18:42
  • @RetiredNinja `char * content = "";` is not correct? After applying the solution of @pm100 I get `munmap_chunk(): invalid pointer Aborted (core dumped)` but when I do not initialize the pointer I get `Segmentation fault (core dumped)` – John Smith Feb 12 '22 at 18:57
  • @JohnSmith yes, i forgot the first time case. realloc is the better answer, but you shoul dbe able to see whats wrong with my answer – pm100 Feb 12 '22 at 19:20