The last remaining bug to slay in my spell-checker for CS50 — after making sure my hash function was implemented properly — is a memory issue. The program works, finds the correct number of misspelled words. But the CS50 bot that checks submissions isn’t going to give a full score yet.

Valgrind is a suite of tools, where Memcheck is the default and most used.

detects memory-management problems, and is aimed primarily at C and C++ programs. When a program is run under Memcheck's supervision, all reads and writes of memory are checked, and calls to malloc/new/free/delete are intercepted.

I am using the CS50 IDE, where Valgrind is already installed. So directly out of the box: I can add valgrind to the beginning of the command to run my program with Memcheck.


Braindump of the debugging 🐛

There is a lot of output, but the parts that look relevant are:

Invalid read of size 8
  Address 0x4b9a550 is 48 bytes inside a block of size 56 free'd

HEAP SUMMARY:
  in use at exit: 472 bytes in 1 blocks
  total heap usage: 12 allocs, 11 frees, 10,552 bytes allocated

ERROR SUMMARY: 7 errors from 1 contexts

Not sure I am reading this correctly, but… 8 bytes? Does this sounds like some sort of off-by-one error? 🤔 That was the reduced test case with tiny dictionary and short text. What will the output be with the large dictionary and longer text…?

Invalid read of size 8
  Address 0x5d11af0 is 48 bytes inside a block of size 56 free'd

HEAP SUMMARY:
  in use at exit: 472 bytes in 1 blocks
  total heap usage: 143,096 allocs, 143,095 frees, 8,023,256 bytes allocated

Still a single alloc more than blocks free'd. Hm. My theory is that there is something off in the details of how I am traversing the linked lists and calling free() on the nodes.

  • Is my for loop looping 1 too few or 1 too many times? No.
  • Also played around with replacing a while loop with a do...while and hey, got a segmentation fault. Which was fun to see in the Memcheck output like this:
Address 0x30 is not stack'd, malloc'd or (recently) free'd

Process terminating with default action of signal 11 (SIGSEGV)
Access not within mapped region at address 0x30

Time to check if everything is working as it should with the cursor variable that moves the pointer to the next node in the linked list. I’ve got a tmp which is what I call free() on, so I don’t think I’m loosing the rest of the linked list after the first node. But ah, I was moving the pointer and free-ing in the wrong order. Fixed! And voilà:

HEAP SUMMARY:
  in use at exit: 472 bytes in 1 blocks
  total heap usage: 12 allocs, 11 frees, 10,552 bytes allocated

 LEAK SUMMARY:
  definitely lost: 0 bytes in 0 blocks
  indirectly lost: 0 bytes in 0 blocks
  possibly lost: 0 bytes in 0 blocks
  still reachable: 472 bytes in 1 blocks
  suppressed: 0 bytes in 0 blocks

ERROR SUMMARY: 0 errors from 0 contexts

So 0 errors, but still an issue. Beginning to feel confident-ish in my unload function, so maybe the bug is in my load function? That I am alloc'ing more that I should? Hm. No.

Noooooooooooo… 🤦🏻‍♀️ Or rather… yay? Because now I know exactly what is still reachable. Lets just say that I will never ever again forget to fclose() 😂

All heap blocks were freed -- no leaks are possible

Spell Checker Submitted 🍾

Yay.


What I Learnt Today 🎓

  • How to use Valgrind and a bit about reading the output
  • Always remeber to close files!
  • Not all memory issues are memory leaks