-1

Today i was trying to write a program and i realized that i need one file to be read in more places inside my program, but before i got there i had the following:

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

char *printFile(char *fileName){
    long int length;
    char *buffer;
    size_t size;
    FILE *file;

    file = fopen (fileName , "r" );


    fseek (file , 0 , SEEK_END);
    length = ftell (file);
    fseek (file , 0 , SEEK_SET);

    buffer = (char *) malloc (sizeof(char)*(size_t)length);
    if (buffer == NULL){
        fputs ("Memory error",stderr);
        exit (2);
    }


    size = fread (buffer,1,(size_t) length,file);
    if (size != (size_t)length){
        fputs ("Reading error",stderr);
        exit(3);
    }

    fclose (file);
    return buffer;
}

int main (void) {
    char *fileName = "test.txt";
    char *fileContent = printFile(fileName);

    printf("%s", fileContent);

    free(fileContent);

    return 0;
}

As you can see i have used free in the main function, and after i realized that that's not ok for my program I decided to free that buffer inside the printFile function, and I now have this:

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

char *printFile(char *fileName){
    long int length;
    char *buffer,*buffer2;
    size_t size;
    FILE *file;

    file = fopen (fileName , "r" );
    if (file == NULL){
        fputs ("File error",stderr);
        fclose (file);
        exit (1);
    }

    fseek (file , 0 , SEEK_END);
    length = ftell (file);
    fseek (file , 0 , SEEK_SET);

    buffer = (char *) malloc (sizeof(char)*(size_t)length);
    if (buffer == NULL){
        fputs ("Memory error",stderr);
        exit (2);
    }

    buffer2 = (char *) malloc (sizeof(char)*(size_t)length);
    if (buffer2 == NULL){
        fputs ("Memory error",stderr);
        exit (2);
    }

    size = fread (buffer,1,(size_t) length,file);
    if (size != (size_t)length){
        fputs ("Reading error",stderr);
        exit(3);
    }

    strcpy (buffer2, buffer);
    fclose (file);
    free(buffer);

    return buffer2;
}

int main (void) {
    char *fileName = "test.txt";
    char *fileContent = printFile(fileName);

    printf("%s", fileContent);

    return 0;
}

As you probably noticed i used a second pointer (*buffer2) to copy the content of the first buffer inside of it before I free it the first one.

My question is: is my approach right or wrong?

Michi
  • 5,175
  • 7
  • 33
  • 58
  • Standard warning: Do not cast `void *` as returned by `malloc` & friends! `sizeof(char)` is _defined_ by the standard to yield `1`. That will never differ. – too honest for this site Jul 24 '15 at 22:16
  • you mean (char *) in front of malloc ? – Michi Jul 24 '15 at 22:18
  • SO is not a code review site. If your code is atually working, you might migrate to code review, but first read their FAQ if that is appropriate. If you have a specific problem, please post a [mcve]. – too honest for this site Jul 24 '15 at 22:18
  • I mean casting `void *` to another pointer. (and `sizeof(char)`) – too honest for this site Jul 24 '15 at 22:18
  • Sorry, i thought that I was explaining enough, and i also showed my code, the question was if i copying buffer to buffer2 and then freeing the main buffer was a good approach. There was no code review my intention. My bad if i did something wrong. – Michi Jul 24 '15 at 22:24
  • 1
    @Michi if your code works as intended and you're wondering if there's a better way to do the same thing, that's *exactly* what [Code Review](http://codereview.stackexchange.com/help/on-topic) is for! – Mathieu Guindon Jul 24 '15 at 22:29
  • i did a wrong approch of the problem, i was only intended somehow to return the content of that file through that function. – Michi Jul 24 '15 at 22:31

2 Answers2

1

Welcome to memory management!

The question you should be asking is: why would your second approach be superior to the former? You free buffer, but then you return buffer2, and someone has to free buffer2, so you are in exactly the same position as before, except that you copied the file contents twice.

If you don't want to allocate memory inside printFile(), then force the caller to pass in a buffer. In other words, shift the responsibility of dealing with allocation to whatever code uses printFile(). Of course, now the caller has to worry about allocating a buffer that is large enough (although it is easy to get the file size with stat(2), or with stdio wrappers like you did).

Whatever method you end up using, you just can't run away from memory management when you start using dynamic allocation: someone, somewhere, will have to be responsible for freeing memory.

Filipe Gonçalves
  • 20,783
  • 6
  • 53
  • 70
  • I think i understand now. I only wanted to return the content of that file, that was all my intention. – Michi Jul 24 '15 at 22:29
  • @Michi I think your original approach is enough. Just make sure you don't forget to free the buffer once you're done - although it might be irrelevant on this short-living program, it's usually good practice. – Filipe Gonçalves Jul 24 '15 at 22:33
  • The problem remains now what Olaf sayed: "Standard warning: Do not cast void * as returned by malloc & friends! sizeof(char) is defined by the standard to yield 1. That will never differ. " i removed this: (char *) if this is what he meant. I just do not understand what he sayed. – Michi Jul 24 '15 at 22:42
  • @Michi He's saying that instead of doing `buffer = (char *) malloc(...)` you should simply do `buffer = malloc(...)`. You don't need the explicit `(char *)` cast. To see why this is bad practice, refer to http://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc – Filipe Gonçalves Jul 24 '15 at 22:44
  • @Michi About `sizeof`, he's saying that `sizeof(char)` is always 1, so you don't need to use that. So instead of `malloc(sizeof(char)*(size_t)length);` you can use the shorter version `malloc(length);`. – Filipe Gonçalves Jul 24 '15 at 22:45
  • if i use "malloc(length);" i get: "error: conversion to ‘size_t’ from ‘long int’ may change the sign of the result [-Werror=sign-conversion]|" so i need to cast length, works only this: "buffer = malloc((size_t)length);" – Michi Jul 24 '15 at 22:52
  • @Michi: **Please** read a C book to get the prases right. As you are using `malloc`, you shoudl already know what `void *` and what a _cast_ is. These are vital terms when using C and understanding any literature. It is a very bad idea to refer to "casting the result of malloc" as "that thing in parenthesis before the word `malloc`". That way you will never really understand what you are doing - and have a very flat learning-curve. – too honest for this site Jul 24 '15 at 22:53
  • I started to read that book you gave me yesterday, and so i got to succed this program today. i use gcc options: "-Wall -g -W -Wall -Wextra -Werror -Wstrict-prototypes -Wconversion" – Michi Jul 24 '15 at 22:55
  • @Michi Ah yeah, that's right, you'll need to do that. But by all means, do take the time to read a good C book. It's well worth it, and I'm sure you'll master the language quickly. – Filipe Gonçalves Jul 24 '15 at 22:56
  • 1
    @Michi: (note: please add `@addressee` to talk to someone. Otherwise your peer will not be notified about your comment). `-W` is the deprecated old version of `-Wextra`. You should also compile C99 or - better C11 code (`-std=C11` or `-std=gnu11` if you want gcc extensions (some are very useful) ). C99/C11 mode implies `-Wstrict-prototypes` as that is part of the newer C standards. Notet that gcc since 5.1 defaults to C11 (C90 for older versions). `-g` is not a warning, but adds debugging symbols. You might want to optimize: `-Og` (since 4.8 iirc) while debugging. ... – too honest for this site Jul 24 '15 at 23:17
  • @Michi: ... The default is `-O2` iirc, which makes debugging complicated to impossible. – too honest for this site Jul 24 '15 at 23:18
  • @Olaf i get: "gcc: error: unrecognized command line option ‘-std=C11’" – Michi Jul 24 '15 at 23:22
  • @Michi: Which version of gcc are you using actually? Must be pretty old. Hmm... try lowercase.× (man, please think a bit for yourself. There is quite a good documentation for gcc). – too honest for this site Jul 24 '15 at 23:36
  • @ Olaf I use: "gcc (Ubuntu 4.9.2-10ubuntu13) 4.9.2" and yes, it is working with lowercase – Michi Jul 24 '15 at 23:37
1

Your approach can be improved.

Instead of

char *printFile(char *fileName);

Use two functions,

char *getFileContents(char *fileName);
void printFile(char *fileName);

With the understanding that getFileContents returns memory that needs to be freed by users of the function. printFile(), and other functions can call getFileContents(), can do with the contents as they please and then call free() on the memory.

Also, when there is an error in reading the contents of the file, return NULL instead of calling exit().

char *getFileContents(char *fileName)
{
   long int length;
   char *buffer;
   size_t size;
   FILE *file;

   file = fopen (fileName , "r" );
   if (file == NULL){
      fputs ("File error",stderr);
      return NULL;
   }

   fseek (file , 0 , SEEK_END);
   length = ftell (file);
   fseek (file , 0 , SEEK_SET);

   buffer = (char *) malloc (sizeof(char)*(size_t)length);
   if (buffer == NULL){
      fclose (file);
      fputs ("Memory error",stderr);
      return NULL;
   }

   size = fread (buffer,1,(size_t) length,file);
   if (size != (size_t)length){
      fputs ("Reading error",stderr);
      fclose (file);
      return NULL;
   }

   fclose (file);

   return buffer;
}

char *printFile(char *fileName)
{
   char* fileContents = getFileContents(fileName);
   if ( NULL != fileContents )
   {
      printf("%s", fileContents);
      free(fileContents);
   }
}
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • The better approach will be to free that pointer just after i do not need that content of that file anymore, in this case the first approach (my first code) it should be enough. isn't it ? – Michi Jul 24 '15 at 22:36
  • @Michi, I agree with that assessment. – R Sahu Jul 24 '15 at 22:40