yijie yan
yijie yan

Reputation: 21

How to use char * in linked list

In a struct Node type, there is a char* data. And each node uses a linked list to combine.

If the type of "data" is INT it is ok. (ex. the age: 23,45,33....) But when the type turn to "char * ", ex. save name: “Jack”,"Jay","Jame". The value is all the same, the later will cover the fronter. ex: First time input: Jack. The list is Jack Second time input: Jay. The list is Jay Jay Third time input:Jame. The list is Jame Jame Jame

The code is like:

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

typedef struct listNode{
 char *data;
 struct listNode *next;
} *ListNodePtr;


typedef struct list {
  ListNodePtr head;
} List;



List new_list(){
  List temp;
  temp.head = NULL;
  return temp;
}



//Student Courses

void insert_at_front(ListNodePtr* head, char *data){ 
 ListNodePtr new_node = malloc(sizeof(struct listNode));
 new_node->data = data;
 new_node->next = *head;
 *head = new_node;
}

void print_list(List *self)
{
   ListNodePtr current = self->head;
   while(current!=NULL)
   {
       printf("%s \n", current->data);
       current = current->next;
   }

   printf("\n");
}


int main()
{
   char i = 'y';
   char *name;
   List mylist = new_list();
   while(i=='y'){
       printf("your name is :");
       scanf("%s",name);
       insert_at_front(&mylist.head,name);
       print_list(&mylist);
   }

   return 0;
}

Upvotes: 2

Views: 10041

Answers (1)

David C. Rankin
David C. Rankin

Reputation: 84561

The good news is your thinking on list operation is not too far off... the bad news is it isn't right-on either. The biggest stumbling blocks you have are just handling the basics of user-input, and insuring your have storage for each bit of data you want to store.

In main() when you declare char *name;, name is an uninitialized pointer. It does not point to any valid memory yet in which you can store the characters that make up name (plus the terminating nul-character). The only thing you can store in a pointer, is a memory address -- and, to be useful, that memory address must be the start of a valid block of memory sufficiently sized to hold what it is you are attempting to store.

In your code, since name does not point to any valid block of memory capable of storing the characters in name, you immediately invoke Undefined Behavior with scanf("%s",name); (Boom! - "Game Over" for your program).

Since you are reading a name, rarely over 64 characters, just use a fixed-size buffer to hold name to pass it to insert_at_front. Don't skimp on buffer size, so just to be sure, you can use something reasonable like 512 bytes. Don't use magic numbers in your code, so if you need a constant:

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

#define MAXN 512    /* if you need a constant, #define one (or more) */
...
int main (void) {

    char i = 'y';
    char name[MAXN] = "";   /* fixed size buf for reading name input */
    ...

Now for your structs, you are using a "wrapper" struct holding head that essentially wraps your list. It is fine to do, though not required. However, since you are using one, you need to either declare it using automatic storage (and pass it as a parameter) or dynamically allocate for it. Since you use a new_list function, if you were using automatic storage, you would have to declare mylist as type List in main() and pass it as a parameter to new_list() for initialization.

Why? You cannot return temp from new_list because temp was declared within new_list (it is local to new_list) and the function stack for new_list (the memory that contains the temp variable) is destroyed (released for reuse) when new_list returns.

If you want to return a pointer from new_list, then you must either (1) pass a previously declared temp from main() to new_list -- or -- (2) dynamically allocate for temp in new_list giving temp allocated storage duration so that the memory containing temp survives until free() is called on that memory, or the program ends. The normal option is (2), though there is nothing wrong with (1) if you adequately account for the storage duration of the variable.

Makes a few tweaks, and avoiding typedeffing a pointer, because It is NOT a good idea to typedef pointers?, and adding a convenient example of the flexibility you have to track list statistics in your wrapper struct, you could do something similar to:

typedef struct lnode {  /* don't typedef pointers -- it will confuse you */
    char *data;
    struct lnode *next;
} lnode;

typedef struct list {   /* you pass this list to insert_at_front */
    lnode *head;
    size_t size;        /* you can track any list stats you like */
} list;

/* create a dynamically allocated list struct */
list *new_list (void) 
{
    list *temp = malloc (sizeof *temp);     /* create storage for list  */
    if (!temp) {                            /* validate ALL allocations */
        perror ("malloc-new_list");
        return NULL;
    }
    temp->head = NULL;  /* initialize head NULL */
    temp->size = 0;

    return temp;    /* return pointer to new list */
}

To help make lists more logical while you are learning, it often helps to create a separate function that is responsible for creating each node you add to your list. This allows you to concentrate on which items within your node struct need storage allocated, and provides a convenient place to handle the allocation (and validation of that allocation), as well as initializing all values in a single place -- without cluttering your list logic. You could implement a create_node function similar to:

/* create new dynamically allocated node, initialize all values */
lnode *create_new_node (const char *data)
{
    lnode *new_node = NULL;

    if (!data) {    /* validate data not NULL */
        fputs ("error: data is NULL in create_new_node.\n", stderr);
        return NULL;
    }

    new_node = malloc (sizeof *new_node);   /* allocate/validate node */
    if (!new_node) {
        perror ("malloc-new_node");
        return NULL;
    }
    /* allocate/validate storage for data */
    if (!(new_node->data = malloc (strlen (data) + 1))) {
        perror ("malloc-new_node->data");
        free (new_node);
        return NULL;
    }
    strcpy (new_node->data, data);  /* copy data to new_node->data */
    new_node->next = NULL;          /* set next pointer NULL */

    return new_node;    /* return pointer to new_node */
}

(note: you have allocated for (1) the list, (2) the node, and (3) the data)

That leaves your logic for insert_at_front clean and readable. Additionally, you need to always use a proper type for any list operation, especially where any allocation is involved, that allows you to gauge success/failure of the list operation. Generally returning a pointer to the node added (a new head here) or NULL on failure, is all you need, e.g..

/* insert new node at front, returning pointer to head
 * to guage success/failure of addition.
 */
lnode *insert_at_front (list *mylist, const char *data)
{ 
    lnode *new_node = create_new_node(data);

    if (!new_node) 
        return NULL;

    new_node->next = mylist->head;
    mylist->head = new_node;
    mylist->size++;

    return mylist->head;
}

In any code you write that dynamically allocates memory, you have 2 responsibilities regarding any block of memory allocated: (1) always preserve a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed. Get in the habit now to cleaning up after yourself -- it will pay big dividends as your programs become more complex. If you are dealing with a list, and nodes, then write a function to free all data, nodes and the list when you are done with it. Something simple is all that is needed, e.g.

/* you are responsible for freeing any memory you allocate */
void free_list (list *mylist)
{
    lnode *current = mylist->head;
    while (current) {
        lnode *victim = current;
        current = current->next;
        free (victim->data);
        free (victim);
    }
    free (mylist);
}

With user input, your are responsible for validating that you received good input and that it satisfies any conditions you have placed on the input. You are also responsible for insuring that the state of the input buffer is ready for the next input operation. That means clearing any extraneous characters that may be left in the input buffer (e.g. stdin) that would cause your next attempt at input to fail. A simple helper function to empty stdin can save you from a world of trouble.

/* you are responsible for the state of stdin when doing user input */
void empty_stdin (void)
{
    int c = getchar();

    while (c != '\n' && c != EOF)
        c = getchar();
}

Further, every input function you will use has a return. You must validate the return of any input function you use to determine if (1) valid input was read, (2) whether the user canceled input by generating a manual EOF, and (3) when using scanf whether a matching or input failure occurred. So always check the return! For example:

    while (i == 'y' || i == '\n') {         /* 'y' or (default '\n') */
        fputs ("\nenter name: ", stdout);
        if (scanf ("%511[^\n]", name) != 1) {   /* ALWAYS CHECK RETURN! */
            fputs ("error: invalid input or user canceled.", stderr);
            return 1;
        }
        empty_stdin();      /* empty any chars that remain in stdin */
        ...

Putting it altogether in a short example, you could do something like the following:

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

#define MAXN 512    /* if you need a constant, #define one (or more) */

typedef struct lnode {  /* don't typedef pointers -- it will confuse you */
    char *data;
    struct lnode *next;
} lnode;

typedef struct list {   /* you pass this list to insert_at_front */
    lnode *head;
    size_t size;        /* you can track any list stats you like */
} list;

/* create a dynamically allocated list struct */
list *new_list (void) 
{
    list *temp = malloc (sizeof *temp);     /* create storage for list  */
    if (!temp) {                            /* validate ALL allocations */
        perror ("malloc-new_list");
        return NULL;
    }
    temp->head = NULL;  /* initialize head NULL */
    temp->size = 0;

    return temp;    /* return pointer to new list */
}

/* create new dynamically allocated node, initialize all values */
lnode *create_new_node (const char *data)
{
    lnode *new_node = NULL;

    if (!data) {    /* validate data not NULL */
        fputs ("error: data is NULL in create_new_node.\n", stderr);
        return NULL;
    }

    new_node = malloc (sizeof *new_node);   /* allocate/validate node */
    if (!new_node) {
        perror ("malloc-new_node");
        return NULL;
    }
    /* allocate/validate storage for data */
    if (!(new_node->data = malloc (strlen (data) + 1))) {
        perror ("malloc-new_node->data");
        free (new_node);
        return NULL;
    }
    strcpy (new_node->data, data);  /* copy data to new_node->data */
    new_node->next = NULL;          /* set next pointer NULL */

    return new_node;    /* return pointer to new_node */
}

/* insert new node at front, returning pointer to head
 * to guage success/failure of addition.
 */
lnode *insert_at_front (list *mylist, const char *data)
{ 
    lnode *new_node = create_new_node(data);

    if (!new_node) 
        return NULL;

    new_node->next = mylist->head;
    mylist->head = new_node;
    mylist->size++;

    return mylist->head;
}

/* print_list - tweaked for formatted output */
void print_list (list *self)
{
    lnode *current = self->head;

    while (current != NULL)
    {
        if (current == self->head)
            printf (" %s", current->data);
        else
            printf (", %s", current->data);
        current = current->next;
    }

    putchar ('\n');
}

/* you are responsible for freeing any memory you allocate */
void free_list (list *mylist)
{
    lnode *current = mylist->head;
    while (current) {
        lnode *victim = current;
        current = current->next;
        free (victim->data);
        free (victim);
    }
    free (mylist);
}

/* you are responsible for the state of stdin when doing user input */
void empty_stdin (void)
{
    int c = getchar();

    while (c != '\n' && c != EOF)
        c = getchar();
}

int main (void) {

    char i = 'y';
    char name[MAXN] = "";   /* fixed size buf for reading name input */

    list *mylist = new_list();

    while (i == 'y' || i == '\n') {         /* 'y' or (default '\n') */
        fputs ("\nenter name: ", stdout);
        if (scanf ("%511[^\n]", name) != 1) {   /* ALWAYS CHECK RETURN! */
            fputs ("error: invalid input or user canceled.", stderr);
            return 1;
        }
        empty_stdin();      /* empty any chars that remain in stdin */

        insert_at_front (mylist, name);         /* insert name */
        fputs ("continue (y)/n: ", stdout);     /* prompt to continue */
        scanf ("%c", &i);   /* read answer (or '\n' from pressing Enter) */
    }

    printf ("\nfinal list (%zu nodes):", mylist->size);
    print_list (mylist);

    free_list (mylist);     /* don't forget to free memory you allocate */

    return 0;
}

(note: the prompt to continue allows the user to simply press Enter to indicate he wants to continue entering names, any other character will exit. That is why (y) is shown as the default in the prompt -- can you explain why and how that works?)

Example Use/Output

$ ./bin/llinshead

enter name: Mickey Mouse
continue (y)/n:

enter name: Donald Duck
continue (y)/n:

enter name: Pluto (the dog)
continue (y)/n:

enter name: Minnie Mouse
continue (y)/n: n

final list (4 nodes): Minnie Mouse, Pluto (the dog), Donald Duck, Mickey Mouse

Memory Use/Error Check

It is imperative that you use a memory error checking program to insure you do not attempt to access memory or write beyond/outside the bounds of your allocated block, attempt to read or base a conditional jump on an uninitialized value, and finally, to confirm that you free all the memory you have allocated.

For Linux valgrind is the normal choice. There are similar memory checkers for every platform. They are all simple to use, just run your program through it.

$ valgrind ./bin/llinshead
==5635== Memcheck, a memory error detector
==5635== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==5635== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==5635== Command: ./bin/llinshead
==5635==

enter name: Mickey Mouse
continue (y)/n:

enter name: Donald Duck
continue (y)/n:

enter name: Pluto (the dog)
continue (y)/n:

enter name: Minnie Mouse
continue (y)/n: n

final list (4 nodes): Minnie Mouse, Pluto (the dog), Donald Duck, Mickey Mouse
==5635==
==5635== HEAP SUMMARY:
==5635==     in use at exit: 0 bytes in 0 blocks
==5635==   total heap usage: 9 allocs, 9 frees, 134 bytes allocated
==5635==
==5635== All heap blocks were freed -- no leaks are possible
==5635==
==5635== For counts of detected and suppressed errors, rerun with: -v
==5635== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Always confirm that you have freed all memory you have allocated and that there are no memory errors.

Look things over and let me know if you have any further questions.

Upvotes: 3

Related Questions