0

I have a method called deleteList that takes a head of a single linked list as an input and it removes all the nodes.

In the method deleteList I can verify that all the nodes are deleted, but when the execution returns back to the main, myList is not empty. So in the subsequent call to LengthOfList, the code fails with an exception.

[Please note that I am unable to change the signature of deleteList]

Here is the code:

#include <iostream>
using namespace std;

typedef struct CodeNode* List;

struct CodeNode
{
    char data;
    List next;

    CodeNode(char new_data, List new_next)
        : data(new_data), next(new_next) {
    }

};

int LengthOfList(List head)
{
    int len = 0;
    for (List ptr = head; ptr != nullptr; ptr = ptr->next) {
        len++;
    }
    return len;
}

void deleteList(List head)
{
    List prev = head;

    while (head)
    {
        head = head->next;
        delete(prev);
        prev = head;
    }
    // I can verify that head is null and all the nodes have been deleted
}

int main(void)
{
    List temp1 = new CodeNode('3', nullptr);
    List temp2 = new CodeNode('2', temp1);
    List myList = new CodeNode('1', temp2);

    cout << "Before " << LengthOfList(myList);
    deleteList(myList);
    cout << "After " << LengthOfList(myList); // CODE FAILS HERE because myList is pointing to a bad memory address (it SHOULD be NULL)
}
TerryB
  • 629
  • 1
  • 5
  • 13
  • 1
    Assigning to a function's (non-reference) argument has no effect outside that function. There is nothing special about pointers. (Also, aliasing pointer types is one of those things that seem like a great idea until it's a terrible idea and you never do it again.) – molbdnilo Feb 17 '23 at 06:33
  • `but when the execution returns back to the main, myList is not empty.` Its free'd memory. Using it is undefined behavior. – tkausl Feb 17 '23 at 06:35
  • `I can verify that head is null and all the nodes have been deleted`. - You can verify this because the `head` pointer is at the end of linked list. – tanmoy Feb 17 '23 at 06:37
  • Note that you shouldn't use the struct keyword together with an identifier as in `typedef struct CodeNode* List;` and instead you should simply use `typedef CodeNode* List;` and besides that, use a using declaration instead of `typedef` like `using List = CodeNode*;` – digito_evo Feb 17 '23 at 06:43

2 Answers2

4

Pass by reference

void deleteList(List& head)

The & makes all the difference. If you want a function to alter a variable in the calling function then you pass by reference.

All your code is doing is modifying the variable head in the function deleteList which is not the same variable as head in main. By using a reference you make head in deleteList an alias for the variable used in the calling function, and therefore changes to it effect that variable.

Another option is to return the changed variable, so in main

myList = deleteList(myList);

and in deleteList

List deleteList(List head)
{
    ...
    return head; // return modified head
}

Both approaches work, it's a style choice which you choose.

I just noticed your comment, 'please note I'm unable to change the signature of deleteList'. Then I'm afraid your code is guaranteed to fail. There is no solution given the peculiar constraints you have been given.

It's amazing how often we see this here, newbie struggling with some problem, but unable to use any of the solutions that any normal programmer would use because of artificial constraints placed on the task by their teacher. In some cases, like yours, these constraints are so severe that there is literally no solution to the task that they've been given. I suggest you ask your teacher for some advice.

john
  • 85,011
  • 4
  • 57
  • 81
  • To add to this, it’s important because a pointer to free’d memory isn’t automatically nullptr. And reading it (comparing it to nullptr) will lead to [undefined behaviour](https://en.cppreference.com/w/c/language/behavior). – George Feb 17 '23 at 06:55
2

In short, your solution is doing exactly this.

#include <iostream>

typedef struct CodeNode* List;
struct CodeNode {
    char data;
    List next;
    CodeNode(char new_data, List new_next): data(new_data), next(new_next) {}
};

int main() {
    List myList = new CodeNode('1', nullptr);
    cout<<myList->data<<endl;
    delete(myList);
    cout<<myList->data<<endl;
    return 0;
}

// given error
// AddressSanitizer: heap-use-after-free on address 
// 0x602000000010 at pc 0x000000342fd6 bp 0x7fffcb6b5110 sp 0x7fffcb6b5108

And, accessing myList after deletion is supposed to be undefined as this is an example of heap use after free which occurs when a program continues to use a pointer after it has been freed.

Useful Links

Solution

john's answer has already given the answer on how passing by reference can actually update the underlying variable and produce the desired effect.

I just wanted to supplement the reason for your understanding.

tanmoy
  • 1,276
  • 1
  • 10
  • 28
  • "it is supposed to fail" actually no, its just undefined – 463035818_is_not_an_ai Feb 17 '23 at 08:39
  • Well, something like, it is supposed to fail to output a value as it is undefined. – tanmoy Feb 17 '23 at 09:07
  • no. Its not supposed to fail to output a value. It can do anything. It could output the value that was previously stored in `data`, nothing wrong with that – 463035818_is_not_an_ai Feb 17 '23 at 09:08
  • Wouldn't it be possible for the `AddressSanitizer` tool to detect that issue before outputting previously-stored data` or doing anything arbitrarily? In a language like rust, these scenario is prohibited even in compile time as I am aware. Pardon my understanding, I am aware that a `delete` or `free` operation can indeed keep the value intact as per its implementation. But, in what scenario would the detection of an AddressSanitizer tool for such scenarios would be disabled or turned off? – tanmoy Feb 17 '23 at 09:26
  • 1
    " But, in what scenario would the output or detection of an AddressSanitizer tool be disabled or turned off?" there is no AddressSanitizer by default. Its a tool used for debugging only. Once you make a release build there is no AddressSanitizer – 463035818_is_not_an_ai Feb 17 '23 at 09:27