
Memory management in C programs - nagriar
http://nethack4.org/blog/memory.html
======
fsloth
Jesus Christ. Let the reader beware indeed. There are horrible suggestions
here that will result in bad code if you think "Hey, that's a cool suggestion,
I'll try that!". Or "Hey, I read in that blogpost this is OK in C, I'll do
this".

For instance: Stack allocation. It's seldom OK to use static variables even
with accessor functions. Wrap the state that the functions operate in structs
that you pass to functions. There are a few places where you cannot avoid
effectively global variables (usually external resources like sockets, window
handles etc) but those should be wrapped in their own subsystems and not
leaked all around the place in global variables. "Still, this is one of the
better reasons to use a global variable." \- congratulations, you are on your
way to Spaghetti Code land. Would you like some black pepper with your
Complexity Bolognese? Protip: Wrap it in a struct and pass it as a variable.
Or redesign your module dependencies.

Too much state in your God-Object. Getting confused? Need better design. C is
not an excuse not to write modular, understandable code where the program flow
is obvious.

Yes, it can get really verbose. If verbosity is an objection, then do not
write in C.

Or, if you dislike verbosity, do what so many others have done. Write your
core routines in C and then write the high level logic in Lua.

Please excuse me my theatricality. I'm not assaulting the author. I'm rather
agravated that this post has this level of visibility and the potential damage
it can do.

~~~
exDM69
> Please excuse me my theatricality. I'm not assaulting the author. I'm rather
> agravated that this post has this level of visibility and the potential
> damage it can do.

I thought it was a good post and provides a good overview of different methods
along with a discussion why you should or shouldn't do some of these things.

I'm pretty certain that in the Linux kernel you can find each and every one of
these methods used more than once and for a good reason.

Maybe in a normal user space app you have the luxury of being able to
malloc/free everything and have nice high level containers, etc available but
when writing C code, the assumption of normal user space apps is not
necessarily true.

All of these methods - and when _not_ to use them - is very useful knowledge.

~~~
Nursie
It's amazing how much you can achieve without malloc and free if you're just
careful to pass in the right output space and buffers as part of your function
call. You rarely really need them for most functional code. (Non-trivial data
structures may be different...)

And the OP is right - globals, especially if not encapsulated within a single
code module, are a prime cause of opaque spaghetti code.

~~~
adestefan
I've written plenty of embedded code where the first rule was "Thou shall not
malloc." In just about every case it turns into setting aside static buffers
and doing manual structure allocation. This is such a common idiom in embedded
systems that most RTOS systems have this functionality built-in.

~~~
Nursie
The embedded system I'm working right now doesn't have malloc as part of its
platform SDK anyway, so it's not like there's a choice :)

I guess it depends on what exactly you're working on, but passing in a stack-
declared for a function to fill with its results seems better to me anyway, as
general practice. Preferable to allocating a new structure in the function and
then returning it, that is. There are no ownership issues and nobody has to
remember to free anything.

Heap emulation with static buffers comes with its own costs - either you end
up writing an allocation engine or you just use a large buffer with an
advancing pointer, free() does nothing and the whole thing has to be reset
every so often, which means that you have to convince the other developers on
the team that it's _not_ to be used like normal malloc and please be careful
and ... that's a whole other can of worms!

------
fjarlq
_"...converting NetHack's codebase to a new language would be very difficult
(even one as similar as C++, which has many potentially relevant details that
differ from C)."_

I was a NetHack devteam member for a few years in the late 1980s and early
1990s, so I got to know the code pretty well... it's pretty hairy, and that
may be putting it mildly.

I wish the NetHack 4 folks would reconsider and explore a complete
reimplementation in a new language. Yes, it would be a much more significant
project, but I think it would be the best thing for the game in the long run.
And it would be a lot of fun, but I'm biased.

I wonder what language would be the best choice at this point, for such a
project.

~~~
bhaak
I'm another NetHack fork developer (with UnNetHack leading a small team and
with NetHack-De having a fun side project), so I also had to think about the
issue of rewriting in a new language.

But this is just a huge undertaking for very little gain. You rewrite the 110K
LOC of the core game engine in a new language so it works the same as before
(which will be difficult because no tests) for having ... what exactly?

If you don't have years to spare, an incremental rewrite would be a viable
option. But do Objective-C or C++ really look like a good choice nowadays?

Then there's also some big non-technical issue. With a rewrite in a different
language you lose the connection to the old code that dates back to the 80s.
It would no longer be a NetHack fork but a NetHack clone. For a fork developer
who already has to fight an uphill battle as the DevTeam is dormant for 10
years but is still claiming that they are working on $NEXT_VERSION, this would
be another of those arguments for "it's not the real thing, I'd rather play
vanilla".

BTW, for a reimagination of NetHack that did something similar, take a look at
[http://www.wazhack.com/](http://www.wazhack.com/) . They weren't that exactly
replicate the NetHack functionality but it at least _feels_ close.

 _I was a NetHack DevTeam member for a few years in the late 1980s and early
1990s, so I got to know the code pretty well... it 's pretty hairy, and that
may be putting it mildly._

For the time it was written in and with the organizational structure it was
developed with, I think it's surprisingly good. After all, it is still C, so
it is not for the faint of heart anyway. Sure, there are things that could be
better, like that monster attacking monster, monster attacking player or
player attacking monster is usually coded in three different places. But for a
code base of this size, it's overall not bad. I find myself cursing more often
about the limitations of C than of NetHack's code itself. :)

 _I wonder what language would be the best choice at this point, for such a
project._

Depending on what your goals are, C could still be the best choice for such a
project.

If you want a program that runs on as many different systems as NetHack does,
C is still hard to beat. Heck, it even works on Android and iOS.

Maybe something like Rust or Go, but we're still not there that C has been
clearly superseded in all domains.

~~~
derefr
> If you don't have years to spare, an incremental rewrite would be a viable
> option. But do Objective-C or C++ really look like a good choice nowadays?

Anything that compiles to native modules and has a clean C FFI would work. You
could encapsulate the current code into a black-box set of APIs, and then
transition those APIs module-by-module from being calls into the old crufty
code, to being calls into shiny new rewritten code. (This is the strategy
recommended at [http://laputan.org/mud/](http://laputan.org/mud/)).

Go is pretty perfect for your use-case, though; it produces portable, low-ish
level application code, and while it's a bit less performant than C, you don't
really need every scrap of C's performance in the way an OS or a 3D game does.
Besides all that, it's very easy for grumpy, staid old C developers to learn,
in the same way that Plan9 is easy for grumpy, staid old Unix administrators
to learn: it's all the same ideas, just streamlined for newer use-cases.

~~~
heinrich5991
I don't think Go is the right option here, while you can call C code from Go,
last time I checked it wasn't possible to call Go from C without having it
called the other way around before. I'd rather suggest going for C++ (or
maaybe Rust... well, not really).

~~~
kibwen
The language may not be mature yet, but C code can very easily call into Rust
code (and, by proxy, any language that can call into C can call Rust code).

------
reality_czech
Memory management in C is really not that hard. If it's big, use malloc or
calloc. If it's small and of fixed size, use the stack. Don't use globals
unless you really, really have a good reason.

Yes, you can get leaks with malloc. But valgrind is actually really good at
finding them. So if you have reasonable unit tests, you can just run valgrind
on all of them and get a lot of coverage.

The problems that Nethack is having are basically self-inflicted. They opted
to use setjmp and longjmp to emulate exceptions. So they don't have a good way
of cleaning up things they malloc'ed. setjmp and longjmp are basically a way
of implementing a cross-function "goto". it's just as bad as it sounds. Not to
mention, they use a lot of functions like sprintf that have basically been
banned by everyone else in the world. Hopefully they're not using gets()...

You don't need to implement crazygonuts memory management unless you're
writing a high-performance database or kernel or something. Certainly not for
a text adventure! Just. Use. Malloc. So this whole thing is just an exercise
in making something really simple into something really complicated. Which I
guess is what Nethack is, anyway.

~~~
deletes
Allocating chunks over ~1024B repeatedly becomes really slow, because malloc
tries to return as much memory back to the system when you free. This takes
magnitudes more time than just a function call. There are some parameters you
can tweak in it, but I think that is the reason many games have their custom
allocator, which malloc a huge chunk of memory and then keep it until the
program exists.

~~~
reality_czech
Some malloc() implementations return memory to the OS during free(). Many
don't. For example, tcmalloc in its default configuration never returns memory
to the OS.

Also, what is "really slow" in the context of nethack? I'm willing to bet that
you could malloc and free till the cows come home and never even notice in
nethack. I bet the whole program doesn't take up more than a few megs of
memory anyway.

------
laichzeit0
Most of these techniques are extremely commonplace in embedded C programs
where you might have a combination of the following: no malloc/free, no
operating system or threading library (everything sits in a void main() {
while (1) {} } loop, you have limited memory and you need to fix the size of
your data structures, you need to reason about the correctness of your code
for safety/security purposes and not doing any dynamic allocation makes this a
lot easier.

I've had to code on embedded devices (where RAM is anything from 4kb to 512kb)
where all the above have been true at some point.

~~~
baruch
I've been working on Storage systems with multi-gigabytes of RAM where you
still reason about your memory in much the same way. These systems have lots
of RAM but properly intend to make use of all of it.

I tend to prefer to allocate on the stack when possible and when the context
is right and that works when working in threads or user-space-threads (aka
coroutines) that are all hierarchical. It makes some things simpler though you
need to care about the stack size and its usage. The places that do need
dynamic memory allocations would normally use a memory pool for same sized
objects to be allocated from.

------
fdej
Nearly all the time, memory management in C can be done using the following
incredibly sophisticated pattern: create an object at the start of a function,
and destroy it at the end of the same function. Use constructor/destructor
functions or macros to hide the details (such as whether the object is a plain
fixed-size struct, or has to allocate additional variable-size memory on the
heap). There are situations where this pattern cannot be used, but they can be
made very rare in properly structured C code.

Finally, write unit tests and run them through Valgrind. That catches almost
all memory-related bugs.

~~~
antimagic
I can see where you're going with this, but I think you're being a little glib
with the "nearly all the time" bit. There are large chunks of functionality
that can't be written that way. The most obvious one is graphical objects,
which are typically expensive to render. You can't be destroying / recreating
them on the fly, the number of graphical elements in use changes radically
over the lifetime of a typical graphical application, and you generally have
an event loop, which means the only function that would keep the objects alive
is at the top level of the event loop.

I personally tend to look at malloc/free in the same way that I look at if
statements. I generally consider code to be cleaner without them, so where
possible I will avoid them, but sometimes that's not possible, which is why
malloc / free (or if statements) exist.

~~~
fdej
Yeah, definitely a little glib. There are certainly prominent exceptions.
Still, I think it's a valid general principle. At least, it works well enough
for me, and I very rarely have to think consciously about memory management in
C. But YMMV, different application domains, and all that.

------
malkia
In consoles games, often the following is found - big double-stack -
predefined buffer, that takes most of the memory. Memory is allocated by
bumping a pointer, for example data for levels loaded increases left -> right
(up->down), and temporary data (characters, temp scene, etc.) right->left
(down->up). When level unloads (or several of it's associated files), memory
is put back before (left or right).

But there are always other heaps (that might or might not work like the stack
above, or have say dmalloc's mspace api used there for other uses), and some
space should be left to CRT itself, nowadays even more with lambda's in C++11
and for other library functions.

For Tools I started to not care, simply because it does not matter that much.
Just make sure _NO_DEBUG_HEAP=1 is set when you run through the Visual Studio
debugger :)

------
jbert
> but if there are no normal returns involved, nothing on the stack will ever
> be deallocated, and your program is effectively just one giant memory leak.

This is true, but there is the glorious exception which is "Cheney on the
MTA":
[http://www.pipeline.com/~hbaker1/CheneyMTA.html](http://www.pipeline.com/~hbaker1/CheneyMTA.html)
[[https://news.ycombinator.com/item?id=7417906](https://news.ycombinator.com/item?id=7417906)]

This describes a way of implementing a scheme->C compiler using CPS which
allows the C functions to never return, and doesn't blow the stack. When the
stack gets big enough, you do a garbage collection run, copying live data to a
new stack. Then, you reset the stack pointer using a judiciously chosen
argument to alloca().

------
userbinator
Detailed articles like this are great for understanding and exposing all the
_freedom_ and _power_ you get with C's memory management philosophy. I agree
with the author's view that it is sufficiently complex that it can force you
to think about _not_ allocating, i.e. "should I really be doing this?" and
thus you often arrive at a simpler, more efficient solution than you would if
you'd used something like Java and thought "I'll just create a new X object".
I see this all the time in C code written by programmers who first learnt Java
- malloc()'ing and copying objects everywhere, often with an underuse of
free() and thus plenty of memory leaks.

My personal preference is for not using fixed-length buffers for storing
things like (non-static) strings, unless I know for certain that they won't
_ever_ grow past a certain length (and document it clearly, so if something
ever needs a length check I can go back and find them); strings of user input
and other possibly indeterminately long things get documented as "limited by
available memory" (i.e. they're dynamically allocated) or "limited to X bytes"
(usually due to a fixed-length buffer somewhere).

As a note, the fragment

    
    
        str = malloc(1 + sizeof "submenu");
        strcpy(str, "submenu");
        return str;
    

could be replaced by a single

    
    
        return strdup("submenu");

~~~
anon4
1) that will call strlen on "submenu"

2) the writer may be using a different malloc than the libc one

3) there is an argument to be made that using malloc plainly like this instead
of hiding it with strdup makes it easy to see the sites in your program where
heap memory is allocated

~~~
0x09
4) `strdup` is a POSIX extension whereas NetHack is cross platform.

~~~
angersock
Easy to fix with a macro.

------
thepumpkin1979
I wish these kind of articles targeted to higher-level programmers would start
teaching to use 'calloc' before 'malloc'. New C users will have a bad time
catching random bugs when they allocate a struct via malloc and immediately
check members using de-referencing in 'if' conditions which will be randomly
evaluated as true because malloc doesn't initialize the memory to zero and
therefore all the members of the struct through the pointer returned by malloc
are full of garbage. My rules are: either use malloc if you intent to
immediately initialize the memory or use calloc and pass the pointer around.

~~~
Nursie
IIRC while this is true and good practice, many modern OS/libc implementations
will zero stuff anyway.

~~~
KMag
Don't count on it.

Modern OSes will zero out pages when your process brk()/ mmap()/etc. them into
your address space, to avoid security problems. However, unless you're using a
debugging version of libc/LD_PRELOAD'ing malloc/free implementations, etc., I
don't believe most malloc/free implementations will necessarily hand you
memory containing a known pattern. Certainly the main selling point of non-
default malloc/free implementations is performance. Zeroing out memory between
uses by the same process has some overhead, and the benefits are more of a
mixed bag than the case of accidentally leaking sensitive data a programmer
has forgotten to zero out (or doesn't think is sensitive) between processes.

Your free() implementation can't return partial pages[0], and in general will
only return multi-page contiguous blocks to the OS, due to memory
fragmentation, anticipation of soon needing to malloc() again, and a desire to
keep system call overhead down. The consequence is that if you do a lot of
malloc()ing and free()ing, you're likely to sometimes get zero'd out pages,
and sometimes get garbage that your process has previously written to memory,
but you should never get garbage that another process has written to memory.
(Unless, of course, it's shared memory or copy-on-write shared with a parent
process.)

[0] Well, if your CPU/MMU supports segment registers or you're willing to do
unsafe things that can't be enforced by the MMU, then your OS could support
returning partial pages to the OS. However, modern desktop/tablet/smartphone
OSes don't work this way.

EDIT: Too lazy to read how to get asterisks to show up consistently. I changed
*aloc to "malloc".

------
yew
Techniques like these are always interesting, even if you don't have much
opportunity/need to apply them. The source for software like NetHack might not
be a good place to look for best practices, but it can be enlightening in
other ways (if you're careful to avoid copying just anything you see).

On that note, while the article doesn't (explicitly) mention it, it would be
wise to remember that VLAs of unbounded size _will_ end up overflowing the
stack eventually. They have their uses, but can be tricky to apply safely. Be
careful!

------
barrkel
Re stack allocation: _It requires you to know how large the object could
possibly be at compile time_

Just wanted to point out that alloca exists pretty much everywhere, so you
don't need to know the exact size at compile time. You still need to know
you're not going to overflow your stack, of course.

There are also variable-sized stack allocated arrays, as in C99 and gcc
extension.

Though IMO the value of a consistent approach outweighs the small performance
advantages of dynamic stack allocation - it would very very rare for me to use
either approach today.

------
ballard
I've worked on embedded C++ network stacks that restrict use of *alloc and new
for various sized buffers and classes that have to be preallocated. That's one
approach.

Just wondering why tcmalloc or boheme GC weren't discussed. Rewriting from
scratch often equivocates to reinventing wheels, and avoidable typing, testing
and maintenance. --effort && ++quality == awesome;

------
bitwize
RAII and smart pointers pretty much make C, and the programming style typical
of C programs, obsolete.

C will always be here for historical reasons and because of all the code
written in it that would be difficult to convert. But new development should
be in C++. Yes, Linus, if you were starting Linux today I'd take you to task
for not using C++. :)

------
jmspring
There is malloc and free.

Anything you do above that to make your life easier is up to you.

C is pretty darn simple in terms of memory management -- you own it, deal with
it. If that is too hard, find a higher level language.

~~~
adamnemecek
What's your point exactly?

~~~
jmspring
I welcome the down votes.

My point - I read the article and agree as described are typical ways of
dealing with memory in C (and in particular within Nethack). At the end of the
day, dynamic memory allocation comes down to tracking mallocs and frees and
whatever extra book keeping you put on top of them.

Reference counting came up. There are libraries that help with such, but you
better understand what that actually means.

Wrapping primitives in higher level abstractions can lead many developers to
wondering why there is a memory leak. Before you embrace the abstractions, you
better darn well understand what is going on under the hood.

I've seen many inexperienced devs get lost in simple valgrind dumps.

~~~
ANTSANTS
You're being downvoted because these comments have little to do with the
content of the article, which is about "dirty" low-level memory management
techniques designed to avoid naive heap allocation, not trying to abstract the
problem away.

------
616c
On a somewhat related tangent, but a tangent just the same, Ada came to my
mind because people in another thread about avionics in relation to the
Malaysia Air Flight were debating the efficacy and/or safety of Ada versus a
language like, for example, Haskell.

When I looked into Ada and memory management, out of curiosity, I did not find
a lot of information about pointers. Or a garbage collection system, and
casual reading confirms that Ada has a less manual version of pointers (from
what I gather) and manual memory management, and no garbage collection. Given
its syntax and semantics, which remind me very, very vaguely of Haskell (which
I am sure is bound to insult someone on HN, specifically Haskellers), I could
not imagine this as a systems language. Then again, even though I am ignorant
of such things and do not know C/C++ (the latter I only took briefly in
undergrad, and I have mixed reactions), I am product of the C/C++ generations
and the stand of the shoulders of Unix and Linux giants, as I have looked into
computer science and Linux application source code in attempt to understand.
Not even write the code mind you, just understand. Perhaps others were exposed
to Pascal and BLISS, so the alternative perspectives made it seem possible. I
do not have that perspective, so I feel dazed and confused by alternative
offerings when reading them. I get the idea generally, but still whisper to
myself with a concerned "How!?", even though it should be more obvious.

In short, I cannot really conceive of manual memory management without
pointers and pointer arithmetic as it presented in C and/or C++. I was
terrible at it, granted, but everything else I see from Rust to other system
languages which promise a systems language without GC and offer some
equivalent of memory management done manually do not click. They seem foreign
and impossible because I have always assumed, for better or worse, C is the
way you interface with a modern PC or embedded system by managing each and
every pointer, or you are on your way to assembly and then memory is one of
many complicated underpinnings you must understand intimately, not just the
must troublesome one.

But I am an amateur programmer. I recently the saw the threads on FORTH. I
want to look because, as much as I "know computers" the idea of this kind of
very low level stack language is very foreign to me, just from looking at it I
feel odd and not sure how it could possibly work. The same would be true of
writing low level code in Haskell. I like it a lot, for all its beauty and
warts, but I have just assumed you must learn to manage such things manually
and in an ugly fashion a la C/C++ to learn system management in a programming
language in our generation, and no amount of screaming or holding your breath
until in the red face will change that.

However, a lot of this is conditioning. Maybe others can share other
observations to counter my admittedly very naive experience.

~~~
kjs3
> Ada and memory management

Ada does not specify GC. Ada does have pointers; it calls them "access types".
It supports unconstrained types, which are allocated on the stack when
declared (no need for malloc/new). It also supports more sophisticated memory
management abstractions, like Storage Pools and Controlled Types.

> I could not imagine this as a systems language.

Ada was designed to be a systems language, provides all the facilities one
would need, and is extensively used as one. So beyond "it's not C" I don't
really know how you could come to that conclusion.

~~~
616c
I saw that access type information, but the docs were not clear to me.

What you question is the central point of my comment: maybe I am biased by
learning C++ on SunOS, then playing with Linux, then using FreeBSD at work,
but it is hard for me to imagine systems programming without C/C++-colored
glasses in terms of memory.

I have no doubt it is possible in Ada. It is just less ugly and dangerous than
C, so I thought you are just forced to deal with pointer management in your
own brain with rough syntactic constraints because that is the highest you can
go wo systems programming on the metal without going to assembly.

I know it seems silly, but that I think drives people to embrace C completely
as the only answer, and not just the preferred answer. Fortunately, forcing
many to deal mean there has been good progress since the OP article was first
published.

~~~
kjs3
I think you're using an awful lot of words to say "if all you have is a
hammer, then every problem looks like a nail". Yes, if all you've ever been
exposed to is C/C++/Unix like environments, then an idiomatic Ada solution to
many programming problems will be conceptually difficult to grasp. That would
be true of any not-C/C++ language or not-Unix environment.

~~~
616c
Sorry, I am wordy because I was busy and overtired. To keep it brief: you seem
to have some knowledge on Ada, can you point me to any interesting open source
projects I can use to understand? I have tried and not found many.

~~~
kjs3
I don't have any particular suggestions for open source Ada projects to
examine (other than, obviously, the GNAT Ada compiler). My experience with Ada
was on decidedly not open source projects, and Ada suffered in this respect in
that there wasn't a good open source Ada compiler until long after interest in
Ada outside of the defense industry faded. Googling for "ada open source"
shows a good number of interesting (if dated) possibilities (these might be a
good place to start:
[http://www.pegasoft.ca/resources/boblap/book.html](http://www.pegasoft.ca/resources/boblap/book.html)
and [http://www.seas.gwu.edu/~mfeldman/ada-project-
summary.html](http://www.seas.gwu.edu/~mfeldman/ada-project-summary.html)),
but not to put to fine a point on it, the cool kids pretty much decided Ada
was the US DoD equivalent of COBOL 20 years ago and ignored it.

As an aside, Ada was intended for large numbers of programmers of varying,
often mediocre skill to cooperate on large programs while minimizing errors
and preventing localized programming errors from propagating too far. That's
something a lot of OSS projects could use.

I also think it's important to understand that Ada was one branch of a family
of languages descended from ALGOL that looked at the problem through a similar
lens (a very not-C/C++ lens). Pascal is the direct ancestor, of course, but in
a addition to Ada, Pascal evolved into a number of other systems programming
languages like Modula-2 & -3 and the various Oberons. Close cousins Mesa &
Cedar were Xerox PARC systems programming languages. There's not a lot left of
the Modula's or Mesa/Cedar (regrettably...Modula-2 was much better than Pascal
for non-academic programming and Modula-3 was a really comfortable all-around
language), but there's a reasonable amount of OSS Oberon laying about.

EDIT: How could I forget...if systems programming is your kink, be sure to
check out UCSD p-code and ETH m-code. These are Pascal/Modula virtual machine
environments and the direct ancestor of the Java VM and M$ CLR.

~~~
616c
And with Ada that is the problem: not much out there. I did see some people
using it for Arduino, I thought that was neat and why it came to my interest
again.

As for Pascal and Oberon, when I saw the computer and OS of the same name
written in Oberon (I think), my mind was blown with how advanced it was.
Thanks for the good comment.

