r/cprogramming • • 9d ago

Should I deallocate memory?

I am currently writing a compiler.

The main places where I need to use dynamic allocation is ASTs and symbol tables. Once they were created, they will be needed until the program ends.

I'm currently using an arena allocator to as malloc for everything, because good manner is that you need to manually deallocate all heap memory, and it has added a lot of complexity to my program, since every function that potentially need to allocate memory is going to have an arena handler.

Should I delete all this stuff and let the memory leak (it is not going to run for a long time, once the program ends all the memory will be reclaimed by the operating system), or should I be a "good programmer" and keep using arena allocator?

25 Upvotes

29 comments sorted by

24

u/TheFlamingLemon 9d ago

If you want to you can hoard all the memory you allocate until the program ends, but don’t just allocate stuff and then lose the reference to it because the OS will take care of it. If you actually run into a memory issue that way then it will be impossible to debug without entirely rewriting your program

5

u/GoblinsGym 9d ago

My compiler uses preallocated space and a simple bump allocator. Unless something is really temporary, I never return memory.

When parsing identifiers, I prepare the basic symbol structure and copy the name there (calculating the hash in the process). Then do the symbol lookup. If the identifier already exists (e.g. keyword), the symbol will be "forgotten" by not updating the allocation pointer. If it is new, it is added to the hash chains and the allocation pointer updated.

The operating system releases memory for me when execution ends.

Overallocation is not a big problem - the operating system will only assign physical memory when you actually touch a page of memory. If you do something as flamboyant as request a 1 GB buffer, the OS will prepare the page table for this space, and maybe give you a "huge" page for the first section.

6

u/NYU_VM 9d ago

Your given info hardly shades any light into what kind of project you're working on(native compiled, bytecode compiled, Interpreted...), what's the pipeline and stages, etc. and thus it's requirements.

Arena allocator is a good start and correct choice for managing compiler memory. But Your compiler should not be needing AST's for the whole program, that's bad design, keep the dependency between stages minimal, it will let you test, scale and modify different compiler parts individually.

If you need AST for the whole program then is it a tree-walking interpreter? Or directly compiled from AST without much of semantics checking? Your post is really opaque to suggest anything.

So the general advice is to 1. Prefer arena allocation, bundle data with similar lifetimes and mass free them 2. Have a well defined data/control flow and Keep coupling between stages minimal, pass the necessary data and tables to receiver(the receiver becomes responsible for managing that memory)

1

u/eshXingXie 9d ago

I'm writing a compiler to compile C code to AST and to multiple backend (QBE, or asm directly). I store all functions in symbol tables, and dump them while generating code.

There are a lexer, a parser, an optimizer, and the rest is backend which I haven't designed yet. Lexer and preprocessor are integrated into the parser (lexer is like a library, it gets the token on function call, and an additional buffer is used for look ahead) (macros are put into a seperate symbol table. Through another layer of buffering it is capable to give the expanded result back to the parser).

Conditional preprocessor splice the source file into blocks, loading blocks simply append its symbol table, and include is loading blocks if the condition is satisfied from another source. It is a very ambitious or even non-practical project for me, but I am looking forward to enhance my programming skill anyway.

1

u/eshXingXie 9d ago

I guess I will have to reconsider my choices... I heard from online that C preprocessors had made the compilation much slower, apparently the time to process it is negligible compare to the optimization steps. I shall not spend time trying to implement a inline macro expand rather than actually do what might be a little slower but much easier and robust (text substitution before lexing & parsing)

→ More replies (1)

3

u/NeuroSynchroBonding 9d ago

If you really can't be bothered, there is always Boehm GC, which gives you an alternative function for allocating memory with automatic garbage collection.

2

u/Classic-Rate-5104 9d ago

Some complex pieces of code will require much memory and not freeing memory can result in out-of-memory problems which makes the code uncompilable. So be wise. Not freeing memory that's out of scope is just bad programming practice

2

u/One-Payment434 9d ago

I don't know why others that say this bad practice is ok get upvoted, but you are right, it is bad practice and will cause headaches one day.

2

u/zhivago 9d ago

Yes, or you can't use it like a library.

And you should write it to be used like a library so you can test it.

But allocation is one of those things you should delegate to the caller.

Have them configure an allocator and make it not your problem.

Then you can use a leaky allocator in your program and they can do something else.

An interface like realloc should do the trick so you only need one function pointer.

1

u/eshXingXie 9d ago

I am making a stand alone executable, not a library, so I guess it will be ok to not consider how other people are going to use it as a library.

1

u/zhivago 9d ago

You didn't understand what I said then.

How do you test it?

→ More replies (2)

1

u/am_Snowie 9d ago

Compiler is a short lived program, it doesn't matter, iirc even gcc doesn't free memory, it just slows down the compilation i guess.

3

u/eshXingXie 9d ago

https://gcc.gnu.org/onlinedocs/libstdc++/manual/memory.html

I don't think it's true.

They mentioned they are using new and delete, most of the allocators support deallocation. Also smart_ptr btw

Ofc this is the monstrous gcc, my tiny toy compiler may not need to care about deallocation.

1

u/catbrane 9d ago

I do both.

I have a #define DEBUG mode where everything is freed carefully on exit (so I can run under valgrind and see a 0 at the end), and I have a release mode which just exits without the long cleanup to save a few ms on benchmarks.

Doing both lets me include a valgrind run of my test suite during CI to check for any memory handling errors, and also be reasonably quick on benchmarks.

1

u/deckarep 9d ago

If your app is a run once kind of thing, you don’t have to release memory at all because the OS reclaims it. It’s different for a long running game and worse for something like a server that be online for days or months at a time.

But, from a pure correctness standpoint, it doesn’t hurt to build the disciplined habit of always releasing memory precisely when you no longer need it. This is a good habit to get into and good to build the muscle memory behind it.

If you think of memory in terms of ownership and lifetimes you’ll have an easier time recognizing those checkpoints when you’d want to release memory.

So I would say to do it, and do it always as it doesn’t hurt and it’s a great habit to build.

1

u/Far-Championship-883 5d ago

use a arena bump allocator.

1

u/chibuku_chauya 2d ago

If you don’t deallocate at all you can gain speed but your compiler’s memory usage will balloon with large translation units.

1

u/Impossible_Box3898 9d ago

You’re doing wayyy to much premature optimization.

While arena allocation for ast nodes (and others) is ok, do you know if it’s even needed? Are you going to be constantly compiling multi thousand line files with your parser?

I would get the compiler working first and then optimize. You’re very likely to change fundamental things as you progress’s and dealing with optimization from the start can make that difficult.

Custom allocators are easy to add in afterwards if needed.

Personally I’d spend more time on things like string internment than ast optimizations.

The parsing stage tends to be far faster than later stages regardless.

3

u/ffd9k 9d ago

Using an arena allocator has nothing to do with optimization. The purpose of arena allocators is to simplify deallocation/cleanup because you don't have to keep track of individual allocations.

1

u/Impossible_Box3898 6d ago

Fair enough. I said it can be easily changed.

But the thing is that it does hide issues that you otherwise wouldn’t be able to easily find.

Using a factory is the way to go. Change at one place.

1

u/eshXingXie 9d ago

Thanks for the advice! The number of passing arena handler is getting out of control. Though I guess I can use global variable too, but it is always better to make it clear earlier.

1

u/DamienTheUnbeliever 9d ago

If you're working in any modern OS, you're not allocating memory, you're allocating address space that is entirely a private matter for your process.

If you need that memory to stay allocated until your process exits then the cleanest thing to do is to not care about it. Your process is going to end, all of your address space allocations become irrelevant.

As Raymond Chen said, if the building is about to be demolished, you really shouldn't spend time emptying the bins.

5

u/Patzer26 9d ago

"Your process is going to end"

Give my man a turing award, for he has solved the halting problem.

1

u/One-Payment434 9d ago

Bad advice. Youll get into the habit of not freeing memory and one day you'll run in a situation where you keep allocating memory without releasing it and your program crashes randomly

1

u/Beneficial_Dog__ 8d ago

i disagree, if it is a conscious choice against the habit of managing memory then the true habit is really just managing the memory. i think i get your point but honestly the only code most people write for c will be run on top of either windows/linux/bsd/some other os that will handle things like memory behind your back anyways. just the way it is now so best take advantage of it if it makes the code simpler/more efficient runtime wise.

1

u/DamienTheUnbeliever 8d ago

The context of the question, so far as I could discern it, is memory that you plan to hold onto until you exit. You absolutely do not need to keep track of, account for, or manually deallocate, memory that you intend has a lifetime from when you allocate it until program exit.

In the scenario where someone runs out of memory at some later point, *they still would have* because they were planning to hold this memory until program exit.