-1

I'm trying to create a linked list inside a function by passing a pointer to the head of the list. Inside the function, everything works perfectly. But when I get back to main(), all of a sudden the pointer is NULL. So if I call the function again, it acts like I'm adding a node for the first time again.

What is the problem with my code?

struct course
{
    int c_ID;
    char *c_name;
    struct course *c_next;
};

void new_course(struct course *c_head, struct course *c_tail); // adds a node

int main ( )
{
    // variable declarations
    int choice;
    char y_n;

    // create linked lists
    struct course *c_head = NULL;
    struct course *c_tail = NULL;

    // print out menu, obtain choice, call appropriate function; loop if desired
    do
    {
        printf("\t\t\t***MENU***\n"
               " 1. Add a new course\n\n"
               ................................
               "Enter the number of the menu option you wish to choose: ");
        scanf("%d", &choice);

        switch (choice)
        {
            case 1:
                new_course(c_head, c_tail);
                if (c_tail == NULL)
                {
                    printf("We're screwed.\n"); // this excecutes every time
                }
                break;
            .....................
        }

        printf("Would you like to return to the main menu? Enter y for yes, n for no: ");
        scanf(" %c", &y_n);

    } while (y_n != 'n' && y_n != 'N');

    // free courses
    struct course *c_temp = NULL;
    c_temp = c_head;
    while (c_temp != NULL)
    {
        c_head = c_head->c_next;
        c_temp->c_ID = 0;     // reinitialize the student ID
        c_temp->c_name[0] = '\0'; // reinitialize the student name string
        free(c_temp->c_name);     // return the string memory to the system
        free(c_temp);         // return the node memory to the system
        c_temp = c_head;      // set temp to next item in the list
    }

    return 0;
}

void new_course(struct course *c_head, struct course *c_tail)
{
    // declare variables
    int ID;
    char name[50];

    // obtain user input
    printf("Enter the course ID number and the course name, separated by a space: ");
    scanf("%d%s", &ID, name);

    if(c_head == NULL) // no courses yet
    {
        c_head = (struct course *) malloc(sizeof(struct course)); // allocate memory for c_head
        c_head->c_next = NULL;
        c_tail = c_head; // update c_tail
    }
    else // the list already has nodes
    {
        c_tail->c_next = (struct course *) malloc(sizeof(struct course)); // allocate memory for new node
        c_tail = c_tail->c_next; // update c_tail
        c_tail->c_next = NULL;
    }

    c_tail->c_ID = ID; // assign ID to c_ID component of new node
    c_tail->c_name = (char *) malloc(sizeof(char) * strlen(name) + 1); // allocate memory for c_name component of new node
    strcpy(c_tail->c_name, name); // assign name to c_name component of new node
    printf("%d = %d, %s = %s\n", c_head->c_ID, ID, c_tail->c_name, name); // this always works, proving the list was created and the assignments worked

    return;
}
Sourav Ghosh
  • 133,132
  • 16
  • 183
  • 261
Cole Dapprich
  • 33
  • 1
  • 7
  • 2
    How many duplicates are there for this question, I wonder? It is a standard problem with linked list code. Pass a pointer to a pointer to the head and tail node to the insertion function (`void new_course(struct course **head, struct course **tail)`) so that you can modify the values in the calling code: `new_course(&c_head, &c_tail)`. Etc. The only novelty in this one is that you are passing both the head and the tail to the insertion function. Also, stylistically, you should separate the data input code from the list manipulation code — you have combined at least two functions into one. – Jonathan Leffler Jul 06 '15 at 06:00

3 Answers3

1

In C, everything is passed by value, including pointers. The values of c_head and c_tail in the caller's context cannot be modified by new_course. To accomplish that, your function signature would need to look like:

void new_course(struct course **c_head, struct course **c_tail)

and throughout the body of new_course you would need to refer to *c_head and *c_tail, as in:

*c_head = (*c_head)->c_next;

and main would have to call it this way:

new_course(&c_head, &c_tail);
Derek T. Jones
  • 1,800
  • 10
  • 18
0

You need to pass pointers to pointers so you can change the value of c_head and c_tail.

Call like this

new_course(&c_head, &c_tail);

Use like this

void new_course(struct course **c_head, struct course **c_tail)
{

    if((*c_head) == NULL) // no courses yet
    {
        (*c_head) = (struct course *) malloc(sizeof(struct course)); 

... etc. etc.

}

I'd not write it like this myself, but that's your problem.

LoztInSpace
  • 5,584
  • 1
  • 15
  • 27
  • Thank you! It works now. So in C++, it would work as I have written it, correct? Also, out of curiousity, how would you write it? Always trying to get better/more efficient. – Cole Dapprich Jul 06 '15 at 06:09
  • In C++ you'd use a template library. How would I do it? I'd encapsulate the head & tail into another structure to keep them together (maybe) maybe write a function to initialise that, not repeat the malloc, use typedef. To be honest it's been so long since I wrote in C, maybe I just went "yuk" because I'd forgotten how unencapsulated it is. – LoztInSpace Jul 06 '15 at 06:22
  • Gotcha. Thanks for your suggestions and help. And yeah, I've only been programming for less than 6 months, and my whole first course was exclusively C++, and now for the second course all of a sudden they're expecting us to write in C right out of the gate. So I agree with you, it is yuk – Cole Dapprich Jul 06 '15 at 06:32
  • C is a great language but once you know it you can generally move on drawing on the concepts you learned. C++ I'm less sure about its utility. It combines a lot of complicated with a lot of difficult with a bit of error prone. There's a whole load of other languages that get you to where you want to go quicker, easier & safer. About the only thing you'll compromise is performance (and possibly disk & memory space) and even then it's rarely worth caring about that to the degree it makes a difference. Good luck with your coding career. – LoztInSpace Jul 06 '15 at 06:43
-1

C uses pass-by-value for function argument passing.

In your case, you're using the function

void new_course(struct course *c_head, struct course *c_tail)

and calling that with

new_course(c_head, c_tail);

No, from the function new_course(), you can change the value pointed to by c_head and c_tail, but you cannot change those two pointers themselves.

If you have to change c_head and c_tail from new_course(), you need to pass a pointer to them, i.e, a pointer to pointer.

Otherwise, you have another option to handle this case. If you want to simply pass the pointer and change the pointer itself from the function, you need to return the modified pointer from the function and collect that to the same variable which you had used as the argument (and changed inside the function). Then, the change will be reflected in the caller function.

That said, as a note,

  1. Please see why not to cast the return value of malloc() and family in C.
  2. sizeof(char) is guaranteed to be 1 in C. Multiplying by the same is redundant and can be avoided.
  3. The recommended signature of main() is int main(void)
Community
  • 1
  • 1
Sourav Ghosh
  • 133,132
  • 16
  • 183
  • 261