Search code examples
arrayscstringdynamic-memory-allocation

Why is freeing the memory after printing the string messing up the print result?


I am reading from a file. The file has following contents:

One Fish Two 2 Red 2 Blue 2
Dr. Seuss
7 fish, 8 2, 8 2, 8 2,
Black 2, 3 2, Old 2, New 2.
This one has a little car.
6 6 6 6 6 star.
Say! What 5 lot of 12 there are.
Yes. Some 3 red, and some 4 blue.
6 3 old 6 6 4 new.
6 3 sad, 6 6 4 glad,
And 4 4 very, 1 bad.
Why 4 they 10 10 10 2 7?
0

I am parsing file and printing like exactly it is at the moment. The dtok.c file that contains the function for parsing strings and have dlmfnt and dlmbck return values.

dtok.c:

#include <string.h>
#include <malloc.h>
#include <assert.h>

char *dtok_r(char *s, const char *delim, char **save_ptr, char **dlmfnt, char *dlmbck) {

    *dlmfnt = NULL;
    *dlmbck = '\0';
  
    char *end;
  
    if (s == NULL)
        s = *save_ptr;
  
    if (*s == '\0') {
        *save_ptr = s;
        return NULL;
    }
    
    /* Scan the length of leading delimiter str.  */
    size_t dlmfnt_len = strspn(s, delim);

    if (dlmfnt_len > 0) {
        *dlmfnt = (char *)malloc((dlmfnt_len + 1) * sizeof(char));
        assert(dlmfnt != NULL && "NOT ENOUGH MEMORY FOR dlmfnt!!!");
        strncpy(*dlmfnt, s, dlmfnt_len);
        *(dlmfnt)[dlmfnt_len] = '\0';
    } 

    s += dlmfnt_len;
    if (*s == '\0') {
        *save_ptr = s;
        return NULL;
    }
  
    /* Find the end of the token.  */
    end = s + strcspn(s, delim);
    if (*end == '\0') {
        *save_ptr = end;
        return s;
    }
    
    /* Terminate the token and make *SAVE_PTR point past it and assign dlmbck */
    if (*end != '\n') {
        *dlmbck = *end;
    }
    *end = '\0';
    *save_ptr = end + 1;
    return s;
}

This is my main.c which is reading from file and parsing it and printing it:

/* Include standard library headers */
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <stdlib.h>
#include <assert.h>
#include <stddef.h>

/* System Constants */
#define MAX_LINE_SIZE 512

int main(int argc, char **argv) {

    assert(argc == 2 && \
        "Program only operates with one file"
    );

    /* Open the file to decompress */
    FILE *dcomp_file = fopen(argv[1], "r");
    assert(dcomp_file != NULL && \
        "Error opening file -->possibly missing<--"
    );
    
    /* Line Buffer for fgets */
    char line_buff[MAX_LINE_SIZE];
    
    /* dtok_r variable inits */
    char *word_token, *save_ptr, dlmbck; 
    static const char *delim = " \n,.?!'\":;-"; /* Delimiter string for dtok_r */

    /* Read line from file until end of file */
    while (((fgets(line_buff, MAX_LINE_SIZE, dcomp_file) != NULL) && \
        (strcmp(line_buff, "0\n") != 0))) {
        save_ptr = line_buff;
        if (strcmp(line_buff, "\n") != 0) {
            char *dlmfnt = NULL;
            while ((word_token = dtok_r(save_ptr, delim, &save_ptr, &dlmfnt, &dlmbck))) {
                if (dlmfnt != NULL) {
                    printf("%s", dlmfnt);
                    //free(dlmfnt);
                }
                printf("%s", word_token);
                putchar(dlmbck);
            }
            putchar('\n');
        }
    }

    /* Close the decomp file */
    int fclose_res = fclose(dcomp_file);
    assert(fclose_res == 0);
    /* Stop screen from disappearing until any character is pressed */
    getchar();
    return EXIT_SUCCESS;
}

What happens here is when I uncomment free(dlmfnt) , I get wrong outputs. but I do not free the array I do not get the wrong output.

Correct Output which I get when I do not use free

One Fish Two 2 Red 2 Blue 2
Dr. Seuss
7 fish, 8 2, 8 2, 8 2,
Black 2, 3 2, Old 2, New 2.
This one has a little car.
6 6 6 6 6 star.
Say! What 5 lot of 12 there are.
Yes. Some 3 red, and some 4 blue.
6 3 old 6 6 4 new.
6 3 sad, 6 6 4 glad,
And 4 4 very, 1 bad.
Why 4 they 10 10 10 2 7?

When I use free the output I get:

One Fish Two 2 Red 2 Blue 2
Dr. Seuss
7 fish, -d8 2, -d8 2, -d8 2,
Black 2, 3 2, -dOld 2, -dNew 2.
This one has a little car.
6 6 6 6 6 star.
Say! What 5 lot of 12 there are.
Yes. Some 3 red, -dand some 4 blue.
6 3 old 6 6 4 new.
6 3 sad, 6 6 4 glad,
And 4 4 very, 1 bad.

Why this happening how can I solve it?


Solution

  • There are some issues in your code:

    • the assertion assert(dlmfnt != NULL && "NOT ENOUGH MEMORY FOR dlmfnt!!!") is incorrect: you do not check for memory allocation failure, but just if dlmfmt is not null. Use assert(*dlmfnt != NULL ...

    • *(dlmfnt)[dlmfnt_len] = '\0' is parsed as *(dlmfnt[dlmfnt_len]) = '\0', which has undefined behavior if dlmfnt_len is non zero, potentially corrupting memory, causing the observed behavior. You might want so simply the code using strndup():

          if (dlmfnt_len > 0) {
              *dlmfnt = strndup(s, dlmfnt_len);
              assert(*dlmfnt != NULL && "NOT ENOUGH MEMORY FOR dlmfnt!!!");
          } 
      
    • when you use putchar(dlmbck) after the last word, you output a null byte, which may not appear on the terminal, but would be stored if output is redirected to a file. You should test:

          if (dlmbck) {
              putchar(dlmbck);
          }