1

At the end of the method, all my test printfs prints the same results. The last line of the file. But current printf in the while loop is working correctly. For some reason my nodes have all the same results. How can I fix it? This is my struct unit:

struct unit
{
    struct unit * next;
    char *name;
};

This is my function for linked list adding lines one by one to the linked list:

void readFile(char fileName[], struct unit * units)
{
    FILE * fp;
    char *line = NULL;
    int length = 1000;
    fp = fopen(fileName, "r");
    int counter = 0;
    int strLength = 0;
    struct unit * current;

    units = (struct units*)malloc(sizeof(struct unit));
    current = units;

    while ( getline(&line, &length, fp) != -1)
    {
        strLength = strlen(&line);
        current->name = (char*)malloc(sizeof(char)* strLength);
        current->next = (struct units*)malloc (sizeof(struct unit));
        strcpy(&current->name, &line);
        printf("\nCurrent: %s",current->name);
        current  = current->next;
        counter++;
    }
    printf("\nTest %s", units->name);
    printf("\nTest %s", units->next->name);
    printf("\nTest %s", units->next->next->name);
    printf("\nTest %s", units->next->next->next->name);
}
rullof
  • 7,124
  • 6
  • 27
  • 36
Marie Fyre
  • 25
  • 3
  • 1
    I'd recommend getting a whiteboard or a sheet of paper and draw it out to follow your logic. That'll help you find the bug. – jia103 Feb 13 '14 at 23:32
  • [Please don't cast the result of malloc in C.](http://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc) – Paul R Feb 13 '14 at 23:37
  • Where is getline defined? – Shmoopy Feb 13 '14 at 23:37
  • What happens if you print `units->name` inside the loop each time? – jia103 Feb 13 '14 at 23:38
  • Why isn't `(struct units *)` giving you, at a minimum, a compiler warning? Are you posting your real code? You're also not allocating enough memory for `current->name`, and almost every time you use the `&` operator you should not be, and you're writing to the wrong addresses as a result. Your compiler should be warning you all over the place, here. – Crowman Feb 13 '14 at 23:42
  • I got some warnings for typecasts and such but they are not really related with the logical bug that I am getting. Even if I fix them, the result is the same. @jia103 If I print units->name each time, it prints same with current->name in the while loop – Marie Fyre Feb 14 '14 at 00:02

3 Answers3

1

Why are you passing in &line into strlen and strcpy? If I remember correctly, you should just pass in line and current->name into these functions. (I don't know about getline though; maybe that's fine as-is.)

jia103
  • 1,116
  • 2
  • 13
  • 20
1

This worked for me (Built and run with a file with several lines. I had to change the getline function for my compiler: also changed several "units" for "unit" which is the name of the struct. Also the line for buffering is statically reserved with a maximum length of 255 characters):

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

struct unit{
    struct unit * next;
    char *name;
};

void readFile(char fileName[], struct unit * units){
    FILE * fp;
    char line[255];
    int length = 1000;
    fp = fopen(fileName, "r");
    int counter = 0;
    int strLength = 0;
    struct unit * current;

    units = (struct unit*)malloc(sizeof(struct unit));
    current = units;

    while ( fgets ( line, sizeof line, fp ) != NULL ) /* read a line */
    {
        strLength = strlen(line);
        current->name = (char*)malloc(sizeof(char)* strLength);
        current->next = (struct unit*)malloc (sizeof(struct unit));
        strcpy(current->name, line);
        printf("\nCurrent: %s",current->name);
        current  = current->next;
        counter++;
    }
    fclose ( fp );

    printf("\nTest %s", units->name);
    printf("\nTest %s", units->next->name);
    printf("\nTest %s", units->next->next->name);
    printf("\nTest %s", units->next->next->next->name);
}

int main(){
    readFile("filename.txt", NULL);
}
drodri
  • 5,157
  • 15
  • 21
  • it worked thanks. Wow I did not see that I wrote "units" instead of "unit". Problem was probably with that. – Marie Fyre Feb 14 '14 at 00:10
  • So does anyone know what the actual bug was? – jia103 Feb 14 '14 at 00:21
  • @jia103 For one thing, the length of the name buffer doesn't include space for the terminating nulchar, so each `strcpy()` is running once-slot passed the end-of-allocated-space. The odd thing is, this example does the same thing, yet was accepted for some reason, when clearly it is invoking undefined behavior. Secondly, this answer also makes the same mistake the OP's code did, namely the last node in the list is always unused (including an empty list). In short, I've no idea why this was accepted much less fixes the problem. – WhozCraig Feb 14 '14 at 00:44
0

Your code has several bad practices, and several bugs. You do not need to preallocate a node before entering your loop. You can simply allocate as-needed. There are a number of ways to ensure the newly allocated node is added to the end of the list using a technique called forward-chaining. I'll get to that in a minute. The following list is in no particular order, but I at least tried to work it top-down


Major: Your readFile() function should return the head of the list it is allocating. If you want to join this to some other list after that, feel free, but the function should start with this:

struct unit* readFile(const char fileName[])

Minor: Note also we're not modifying the file name, so there is no reason to pass it as mutable, thus it is non-const.


Major: Check your file open operation for success before using it:

fp = fopen(fileName, "r");
if (fp == NULL)
{
    perror("Failed to open file.");
    return NULL;
}

Major: Use properly typed variable for the API calls your making. The function getline(), a non-standard extension, is prototyped as:

ssize_t getline(char ** , size_t * , FILE *)

It returns a ssize_t (a "signed" size-type) and takes a size_t* for the second parameter. You're passing the address of an int variable, length, as the second parameter. This is no guarantee the two are compatible types. Fix this by declaring length as the proper type; size_t

size_t length = 0;

Minor: The same issue happens with the return value type of strlen(), which is also size_t, but that will become unimportant in a moment as you'll soon see.


Major: Your use of getline() apart from the second parameter mentioned before is almost correct. The initial input on the first loop is the address of a NULL pointer and a 0-valued length. With each iteration if the buffer already allocated in the previous loop is big enough, it is reused. Unfortunately reading a shorter line, then a longer, then a shorter, and then longer will introduce extra allocates that aren't needed. In fact, You can forego your malloc() logic entirely and just use getline() for allocating your buffer, since it is documented to use malloc() compatible allocation. Therefore, using your existing logic (which we will go over lastly):

while ( getline(&line, &length, fp) != -1)
{
    // note: added to throw out empty lines
    if (length > 0)
    {
        // note: added to null out trailing newline. see the 
        // documentation of getline() for more info.
        if (line[length-1] == '\n')
            line[length-1] = 0;
    }

    if (line[0] != 0)
    {
        // other code here
        current->name = line;
    }
    else
    {   // not using this. release it.
        free(line);
    }

    // reset line and length for next iteration
    line = NULL;
    length = 0;
}

Major: Your original algorithm never free()d the line buffer once you were done with it, thereby introducing a one-time memory leak. Using the above alternative, you need not worry about it.


Alternate: Finally, the list population loop can be made more robust by applying everything discussed so far, and adding to it a technique called forward-chaining. This technique uses a pointer-to-pointer pp that always holds the address of the pointer that will receive the next node allocation. If the list is initially empty( and it is), it holds the address of the head pointer. With each new node added pp is assigned the address of the last node's next member. When the loop is complete (even if i didn't add any nodes), we finish by setting *pp = NULL to terminate the list.

This is the final code base for readFile. I hope you find it useful:

struct unit* readFile(char fileName[])
{
    FILE * fp;
    char *line = NULL;
    size_t length = 0;

    // used for populating the list
    struct unit *head = NULL;
    struct unit **pp = &head;

    // open file
    fp = fopen(fileName, "r");
    if (fp == NULL)
    {
        perror("Failed to open file");
        return NULL;
    }

    while ( getline(&line, &length, fp) != -1)
    {
        // note: added to throw out empty lines
        if (length > 0)
        {
            // note: added to null out trailing newline. see the
            // documentation of getline() for more info.
            if (line[length-1] == '\n')
                line[length-1] = 0;
        }

        if (line[0] != 0)
        {
            // allocate new node
            *pp = malloc(sizeof(**pp));
            if (*pp != NULL)
            {
                (*pp)->name = line;
                pp = &(*pp)->next;
            }
            else
            {   // could not allocate a new node. uh oh.
                perror("Failed to allocate new node");
                free(line);
                break;
            }
        }
        else
        {   // not using this. release it.
            free(line);
        }

        // reset line and length for next iteration
        line = NULL;
        length = 0;
    }
    *pp = NULL;

    return head;
}
WhozCraig
  • 65,258
  • 11
  • 75
  • 141