Skip to content

Conversation

@rozbf
Copy link

@rozbf rozbf commented Jan 24, 2023

Currently, when using --stdlib:zero, all heap-allocated data are memory leaks since there is no GC.

This PR adds the ability to manually free heap-allocated objects using the GC.SuppressFinalize method. Why this method?

  1. It's a standard method from the dotnet API that takes an object.
  2. When using the dotnet runtime, calls to GC.SuppressFinalize are no-ops unless the type has a finalizer.
  3. When implementing the IDisposable interface, GC.SuppressFinalize is typically called at the end when all managed resources have been released. It makes sense to deallocate the object itself at that point.

Example:

using System;

class Work : IDisposable
{
    public string Name { get; set; } = nameof(Work);

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
    }
}

class Program
{
    public static void Main()
    {
        while(true)
        {
            using var x = new Work();
            Console.WriteLine(x.Name);
        }
    }
}

It is also possible to have nested objects if the IDisposable interface is implemented correctly. Arrays also work, but can't implement IDisposable, so they have to be freed by calling GC.SuppressFinalize directly.

I haven't tested the Linux and UEFI code. Feel free to edit this PR as needed.

@MichalStrehovsky
Copy link
Member

Thanks!

I'll think about this a bit. I've been scratching my head on how to best approach this too. Repurposing GC.SuppressFinalize is definitely an out-of-the-box idea. I'm conflicted because on one hand it kind of is in the spirit of zerolib (no new API that doesn't exist in .NET), but on the other hand the meaning is different.

@squidink7
Copy link

Perhaps this could involve the NativeMemory class (as without a GC, all zerolib memory is native memory), which has dedicated methods for alloc and free. Although the API ergonomics of those functions leave much to be desired..

@rozbf
Copy link
Author

rozbf commented Jan 27, 2023

on one hand it kind of is in the spirit of zerolib (no new API that doesn't exist in .NET), but on the other hand the meaning is different

I don't think there is a solution that satisfies both. The closest thing C# has to manual memory management is the dispose pattern, which uses the GC.SuppressFinalize method to inform the garbage collector that the object has been disposed. Repurposing this method seems quite natural and together with the using keyword supported by the language, it produces something similar to std::unique_ptr in C++ (but without the ownership checks).

Btw, I also considered this approach:

int id = GC.GetGeneration(obj);
GC.Collect(id, GCCollectionMode.Optimized);

It is more explicit, but the implementation is complicated and it has a much higher overhead in .NET than GC.SuppressFinalize.

Perhaps this could involve the NativeMemory class

While it might be useful to provide access to the already implemented alloc/free functions via the NativeMemory class, it doesn't solve the problem of memory leaks when using managed reference types.

@bartimaeusnek
Copy link

How about adding a Method to object called Free, which can be called on anything, like ToString, Equals or GetHashCode?

@CypherPotato
Copy link

CypherPotato commented Aug 22, 2023

Why not just

unsafe static bool Free(void* ptr)
{
    ...
}

?

Since it ins't .NET, I see no point in creating an "GC" class for this. There is no GC here.

@rozbf
Copy link
Author

rozbf commented Aug 22, 2023

Why not just

See here:

Public API surface that doesn't exist in .NET cannot be added (i.e. source code compilable against zerolib needs to be compilable against .NET).

@trufae
Copy link

trufae commented Aug 22, 2023

Shouldn't this be void Free?

@CypherPotato
Copy link

Shouldn't this be void Free?

it could return an boolean indicating if the memory was freed or not.

@tajOpti
Copy link

tajOpti commented Nov 6, 2023

Well if there are memory leaks why not implement a profiler that constantly tracks allocations and deallocations? I mean profilers are possible with native aot now for .NET

@Dwedit
Copy link

Dwedit commented Jan 3, 2024

Freeing an object that's on the call stack turns your code into a minefield. Your this pointer now points to freed memory. You can't safely access any class fields or virtual methods at this point.

Also looking through the standard .NET framework, many functions use GC.SuppressFinalize on objects that aren't ready to actually be freed from memory. For example, the class System.IO.Stream. Close calls GC.SuppressFinalize. Dispose is implemented by calling the virtual function Close. So if you call Close, you can't call Dispose afterwards without a crash.

@rozbf
Copy link
Author

rozbf commented Jan 3, 2024

Freeing an object that's on the call stack turns your code into a minefield. Your this pointer now points to freed memory. You can't safely access any class fields or virtual methods at this point.

This is no different from C/C++, which also has manual memory management. Originally, I was looking for a dotnet method that takes a ref object parameter, which could then set the reference to null, but it's kinda pointless since there may be other references to the deallocated object.

Also looking through the standard .NET framework, many functions use GC.SuppressFinalize on objects that aren't ready to actually be freed from memory. For example, the class System.IO.Stream. Close calls GC.SuppressFinalize. Dispose is implemented by calling the virtual function Close. So if you call Close, you can't call Dispose afterwards without a crash.

Yes, you should not call anything on the freed object. That's a responsibility the developer takes on when working with Zerolib. Zerolib will only work with specially tailored code that follows its constraints. The goal is that the same code should also work perfectly fine when compiled and run with dotnet. The reverse is not necessarily true - you can write code that will work fine with dotnet, but will crash with Zerolib.

@ghost
Copy link

ghost commented Feb 22, 2024

Is it possible to use libc's free function on .NET object?

@Tajbiul-Rawol
Copy link

@iahung2 do you mean consuming windows API implementations? One question that I have idk If it is a practical question or not: But is it possible to reimplement these methods in C# by peeking the windows API implementations?

@ghost
Copy link

ghost commented Feb 23, 2024

@iahung2 do you mean consuming windows API implementations? One question that I have idk If it is a practical question or not: But is it possible to reimplement these methods in C# by peeking the windows API implementations?

My question is simple. I have a class called MyClass. I created an object MyObject of type MyClass using new (heap allocation). As it's said on this thread, any heap allocation is a memory leak because there is no GC. I don't want to have a memory leak. So, I need a way to deallocate the memory. I want to know if something like free(MyObject); will work or not.

@Tajbiul-Rawol
Copy link

Tajbiul-Rawol commented Feb 24, 2024

@iahung2 as far as I know, is you can't, allocating in heap isn't preferred and instead of reference types use structs as @MichalStrehovsky suggested to use stack allocation instead of heap, as heap allocation will eventually run out of memory due to no GC, you can try to see if free deallocates or not but this was my understanding from this statement.

https://twitter.com/MStrehovsky/status/1728378901188235301?t=NnuY-TJHNsztAasdfLQAxQ&s=19

@ghost
Copy link

ghost commented Feb 28, 2024

@Tajbiul-Rawol My intuition tells me that free doesn't work. I only asked to be sure. @MichalStrehovsky You must do something. If there is no GC, you must add the ability to do manual memory management. Otherwise, your zerolib is only a toy, or the use cases of it will be very limited, for example, in resource-constrained environments like UEFI, where heap allocation isn't preferred. But for all other use cases, it's useless.

@tajOpti
Copy link

tajOpti commented Feb 28, 2024

@iahung2 I agree. The tool feels very limited in what it can do, maybe because it is a brand new tool, but with no manual memory collection system for heap it is severely limiting. @MichalStrehovsky hope you change your decision and implement something to handle deallocations.

@FrankRay78
Copy link

I'm going to give heap allocation for nostdlib a shot by overriding the .Net new() operator, as per my comment here: #138 (comment)

But if anyone wants to beat me to it, please feel free 😉

@andrew-skybound
Copy link

Here’s another idea: how about overriding GC.KeepAlive() to free an object? (Instead of GC.SuppressFinalize)

It might sound odd at first, but when you think about it, the purpose of KeepAlive is to ensure that the object stays alive until KeepAlive is called. Nobody calling that method should have the expectation that the object would live any longer, so in theory, it should be safe to release at that point.

@Tajbiul-Rawol
Copy link

Ultimately it comes down to the meaning of the method because no new methods will be added and it will be based on an existing api.

@larkliy
Copy link

larkliy commented Jun 10, 2025

you can import the LocalFree function and pass it an argument in the form of &classInstance (this way you will pass the MethodTable of the instance.)

@cyraid
Copy link

cyraid commented Aug 28, 2025

Don't mean to be "that guy", but what stops someone from forking this project (which doesn't look like it's worked on very much), Michal is a very busy guy and this is just his personal project. Someone could create a fork and then those that are serious about it and want to advance it can do it at a faster pace and make it more useable other than a personal toy project.

@ghost ghost mentioned this pull request Nov 16, 2025
@Tajbiul-Rawol
Copy link

@MichalStrehovsky will this issue be resolved or should this be closed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.