【发布时间】: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