Thank you for stopping by! I am a full-stack developer that combines the power of entrepreneurship and programming to make the lives of programmers easier.
Great to see a fellow low-level programmer on here!
I worked on a game engine written in C and was having many issues related to wrongly using the realloc function for dynamically allocated memory. What I did was forget to assign the reallocated memory's pointer to the return value of the function. It took me weeks before I found the underlying problem since only in some cases it would blow up. How would you go about debugging a situation like:
int*p=calloc(5,sizeof(int));// some coderealloc(p,6*sizeof(int));// notice no assignment
Do you use some sort of special tools? Or just some coding standards to not let this happen?
Whenever I'm working with memory, I pair two different tools: Valgrind and Goldilocks (PawLIB).
Valgrind is a pretty ubiquitous tool on UNIX platforms which will show me all of the memory issues encountered while running, even if the undefined behavior doesn't cause any overt problems. My code isn't done until it's Valgrind-pure. However, Valgrind only monitors the execution, so...
Goldilocks is a testing framework I developed at MousePaw Media, as a part of PawLIB. You could technically use any testing framework, but the benefit to Goldilocks is that it bakes the tests into the final executable, instead of requiring an additional framework to run the tests. That way, you can start the normal executable, run each of the tests you wrote, and see which ones Valgrind complains about.
Mind you, this does require you to write a lot of comprehensive behavioral tests...but you really should be doing that anyway in production code. ;)
I should add, I use another tool from PawLIB called IOChannel - basically, a std::cout wrapper - that allows me to cleanly print the address and raw memory from literally any pointer, without having to use a debugger. This can make debugging some problems infinitely easier, especially when you're contending with a Heisenbug that goes away if compiled with -g, but appears when compiled with -O2.
Thank you for stopping by! I am a full-stack developer that combines the power of entrepreneurship and programming to make the lives of programmers easier.
Unfortunately, I didn't find a version of Valgrind for Windows. I tried DrMemory but, after lots of struggle, it didn't give me any helpful information and dropped the ball. Do you have experience with low-level on Windows or just work exclusively on Linux since it is more convenient?
I rarely use Windows for development, as its development toolchain is almost invariably miles behind its UNIX-based counterparts.
If you're on Windows 10, I strongly recommend setting up the Windows Subsystem for Linux [WSL]. That will give you access to the Linux development environment for compiling and testing. Then, use the LLVM Clang compiler on both the WSL and the Visual Studio environments. That way, once you know it compiles and runs Valgrind-pure on WSL, you can trust that it will work on VS Clang.
My approach for this specific problem is to use a compiler that warns about unused return value, such as gcc or clang. I know that stdlib.h on Linux and Mac OS X already decorates realloc() with warn_unused_result attribute.
But just naively setting p = realloc(p, ...) is also wrong, since if the allocation fails, p would be set to NULL but the original object is still allocated. The original pointer is lost and now a memory leak. Use reallocf() which frees the original memory if it could not be resized.
Thank you for stopping by! I am a full-stack developer that combines the power of entrepreneurship and programming to make the lives of programmers easier.
@liulk
Ha, I completely forgot to mention Clang! It does indeed have the best warnings of any compiler I've used. I almost always compile with -Wall -Wextra -Wpedantic -Werror; that last one (as you know, although the reader might not) causes the build to fail on any warnings.
I also use cppcheck as part of my autoreview workflow, and resolve all linter warnings before committing to the production branch.
@codevault
You're right, reallocf() would just free the memory and cause data loss, so it would serve a different use case than realloc(). The more general solution would be to always use this pattern, which is more verbose:
void*q=realloc(p,new_size);if(q==NULL){// do error handling.return;}p=q;
I just find that in most of my use cases, I would end up freeing p in the error handling, so I would just use reallocf() which results in less verbose code.
Thank you for stopping by! I am a full-stack developer that combines the power of entrepreneurship and programming to make the lives of programmers easier.
Great to see a fellow low-level programmer on here!
I worked on a game engine written in C and was having many issues related to wrongly using the
realloc
function for dynamically allocated memory. What I did was forget to assign the reallocated memory's pointer to the return value of the function. It took me weeks before I found the underlying problem since only in some cases it would blow up. How would you go about debugging a situation like:Do you use some sort of special tools? Or just some coding standards to not let this happen?
Whenever I'm working with memory, I pair two different tools: Valgrind and Goldilocks (PawLIB).
Valgrind is a pretty ubiquitous tool on UNIX platforms which will show me all of the memory issues encountered while running, even if the undefined behavior doesn't cause any overt problems. My code isn't done until it's Valgrind-pure. However, Valgrind only monitors the execution, so...
Goldilocks is a testing framework I developed at MousePaw Media, as a part of PawLIB. You could technically use any testing framework, but the benefit to Goldilocks is that it bakes the tests into the final executable, instead of requiring an additional framework to run the tests. That way, you can start the normal executable, run each of the tests you wrote, and see which ones Valgrind complains about.
Mind you, this does require you to write a lot of comprehensive behavioral tests...but you really should be doing that anyway in production code. ;)
I should add, I use another tool from PawLIB called IOChannel - basically, a
std::cout
wrapper - that allows me to cleanly print the address and raw memory from literally any pointer, without having to use a debugger. This can make debugging some problems infinitely easier, especially when you're contending with a Heisenbug that goes away if compiled with-g
, but appears when compiled with-O2
.Thanks for the response!
Unfortunately, I didn't find a version of Valgrind for Windows. I tried DrMemory but, after lots of struggle, it didn't give me any helpful information and dropped the ball. Do you have experience with low-level on Windows or just work exclusively on Linux since it is more convenient?
I rarely use Windows for development, as its development toolchain is almost invariably miles behind its UNIX-based counterparts.
If you're on Windows 10, I strongly recommend setting up the Windows Subsystem for Linux [WSL]. That will give you access to the Linux development environment for compiling and testing. Then, use the LLVM Clang compiler on both the WSL and the Visual Studio environments. That way, once you know it compiles and runs Valgrind-pure on WSL, you can trust that it will work on VS Clang.
My approach for this specific problem is to use a compiler that warns about unused return value, such as gcc or clang. I know that stdlib.h on Linux and Mac OS X already decorates realloc() with warn_unused_result attribute.
stackoverflow.com/a/2889601
But just naively setting
p = realloc(p, ...)
is also wrong, since if the allocation fails, p would be set toNULL
but the original object is still allocated. The original pointer is lost and now a memory leak. Use reallocf() which frees the original memory if it could not be resized.That's a really nice feature, didn't know about it.
But wouldn't that mean data loss in case the memory can't be resized? Wouldn't that become an unrecoverable error?
@liulk Ha, I completely forgot to mention Clang! It does indeed have the best warnings of any compiler I've used. I almost always compile with
-Wall -Wextra -Wpedantic -Werror
; that last one (as you know, although the reader might not) causes the build to fail on any warnings.I also use
cppcheck
as part of my autoreview workflow, and resolve all linter warnings before committing to the production branch.@codevault You're right, reallocf() would just free the memory and cause data loss, so it would serve a different use case than realloc(). The more general solution would be to always use this pattern, which is more verbose:
I just find that in most of my use cases, I would end up freeing p in the error handling, so I would just use reallocf() which results in less verbose code.
I see, that makes sense. I can see myself freeing the memory most of the time when reallocation fails.
Good to note. Thanks!