【问题标题】:Manual memory manager in C# - just a researchC# 中的手动内存管理器 - 只是一项研究
【发布时间】:2016-07-18 14:19:37
【问题描述】:

我正在做一些研究,我实现了一个简单的内存管理器系统来查看进程的行为,但我注意到 MemoryHelper 类的 free 方法和“Marshal.FreeHGlobal”没有实现内存中的空闲,即使在释放内存后也可以访问数据...

using System;
using System.Runtime.InteropServices;

namespace ConsoleApplication3
{
    public unsafe static class MemoryHelper
    {
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern IntPtr GetProcessHeap();

        [DllImport("kernel32.dll", SetLastError = false)]
        static extern void* HeapAlloc(IntPtr hHeap, uint dwFlags, UIntPtr dwBytes);

        [DllImport("kernel32.dll")]
        static extern void* HeapReAlloc(IntPtr hHeap, uint dwFlags, IntPtr lpMem, UIntPtr dwBytes);

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool HeapFree(IntPtr hHeap, uint dwFlags, void* lpMem);

        private static uint HEAP_REALLOC_IN_PLACE_ONLY = 0x00000010;
        private static uint HEAP_NO_SERIALIZE = 0x00000001;
        private static uint HEAP_ZERO_MEMORY = 0x00000008;
        private static IntPtr Handle { get; set; } = GetProcessHeap();

        public static void* Malloc(uint size)
        {
            var tmp = HeapAlloc(Handle, HEAP_ZERO_MEMORY, new UIntPtr(size));
            if (tmp == null)
                throw new Exception("Fail to allocate memory");
            return tmp;
        }

        public static void* Realloc(IntPtr blk, uint newSize)
        {
            var tmp = HeapReAlloc(Handle, HEAP_REALLOC_IN_PLACE_ONLY, blk, new UIntPtr(newSize));
            if (tmp == null)
                throw new Exception("Fail to reallocate memory");
            return tmp;
        }

        public static bool Free(void* blk)
        {
            var ret = HeapFree(Handle, HEAP_NO_SERIALIZE, blk);
            if (!ret)
                throw new Exception("Fail to free memory");
            return ret;
        }
    }

    unsafe public class Study
    {
        public Study()
        {
            UsingManualMemoryClass();
            Console.WriteLine("".PadLeft(80, '='));
            UsingMarshalClass();
        }

        private static readonly object LockObj = new object();
        public void UsingManualMemoryClass()
        {
            int tamanho = 20;
            int* teste = (int*)MemoryHelper.Malloc((uint)(sizeof(int) * tamanho));

            Random rnd = new Random();
            lock (LockObj)
            {
                for (int i = 0; i < tamanho; i++)
                    teste[i] = rnd.Next(0,8000);
            }

            for (int i = 0; i < tamanho; i++)
                Console.WriteLine(teste[i]);

            MemoryHelper.Free(teste);

            Console.WriteLine("".PadLeft(80, '-'));
            for (int i = 0; i < tamanho; i++)
                Console.WriteLine(teste[i]);
        }

        public void UsingMarshalClass()
        {
            int tamanho = 20;
            int* teste = (int*)Marshal.AllocHGlobal(sizeof(int) * tamanho);

            Random rnd = new Random();
            lock (LockObj)
            {
                for (int i = 0; i < tamanho; i++)
                    teste[i] = rnd.Next(0, 8000);
            }

            for (int i = 0; i < tamanho; i++)
                Console.WriteLine(teste[i]);

            Marshal.FreeHGlobal((IntPtr)teste);

            Console.WriteLine("".PadLeft(80, '-'));
            for (int i = 0; i < tamanho; i++)
                Console.WriteLine(teste[i]);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            new Study();
        }
    }
}

为什么会这样?

----- 编辑

我在codeproject 找到了这篇文章,我得到了 C++/CLI 代码,用该代码创建了一个简单的 DLL,并做了一些更改以使用原生 C 运行时来分配和释放内存

#include <malloc.h>
#include <stdlib.h>>

using namespace System;

namespace mm
{
    public ref class Unmanaged abstract sealed
    {
    public:
        generic <typename T> where T : value class
            static void* New(int elementCount)
        {
            return malloc(sizeof(T) * elementCount);
        }

        static void Free(void* unmanagedPointer)
        {
            free(unmanagedPointer);
        }

        generic <typename T> where T : value class
            static void* Resize(void* oldPointer, int newElementCount)
        {
            return realloc(oldPointer, (int)sizeof(T) * newElementCount);
        }
    };
}

使用原生的C函数来做这个,free后无法访问内存...

【问题讨论】:

  • 为什么会发生这种情况? 在非托管语言中,您可以在释放内存后很好地访问它。它会触发未定义的行为,但您可以做到。托管语言应该是解决这个问题的方法。
  • 我认为它可以工作只是因为该内存尚未被重新使用和覆盖。最终它将被重新使用,您将获得垃圾数据或异常。通常与任何其他基于指针的内存访问的想法相同。 C# 称它为 unsafe 因为你可以做这样的傻事。
  • “如何修复这个 [...] ?” - 没有任何损坏,因此无需修复。
  • 我做了一些修改...
  • 分配 20 * 4 字节通常不足以让操作系统释放地址空间。这种小的分配是从低碎片堆中进行的,释放它会将块添加到空闲块列表中。本机 C 代码在附加调试器时的工作方式不同,它在启用调试堆的情况下运行。当 C 程序搞砸内存管理时,它会清理内存块以增加硬异常的可能性,这是一个非常常见的 C 错误。获得相同的结果需要将 _NO_DEBUG_HEAP 环境变量设置为 1。

标签: c# winapi memory-management


【解决方案1】:

Marshal.FreeHGlobal 调用 Win32 API 函数LocalFree。如果您在调用LocalFree 后尝试访问内存,那么您正在调用未定义的行为。一些可能的结果:

  • 您可能会遇到运行时错误。
  • 您可能仍然可以访问之前状态的内存。
  • 您可能仍然可以访问内存,但它已被修改,因为它已被重新使用。
  • ...

几乎任何事情都可能发生,你不能依赖任何事情。

你的错误是推断释放内存后能够访问意味着内存没有被释放。这种逻辑推论是错误的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-10
    • 2011-02-20
    • 2011-05-04
    • 2014-07-29
    • 2014-01-03
    • 1970-01-01
    相关资源
    最近更新 更多