C#中对象的内存地址

lej*_*jon 66 .net c#

我有一段时间写的功能(对于.NET 3.5),现在我已升级到4.0

我无法让它发挥作用.

功能是:

public static class MemoryAddress
{
    public static string Get(object a)
    {
        GCHandle handle = GCHandle.Alloc(a, GCHandleType.Pinned);
        IntPtr pointer = GCHandle.ToIntPtr(handle);
        handle.Free();
        return "0x" + pointer.ToString("X");
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,当我调用它时 - MemoryAddress.Get(新车("蓝色"))

public class Car
{
    public string Color;
    public Car(string color)
    {
        Color = color;
    }
}
Run Code Online (Sandbox Code Playgroud)

我收到错误:

对象包含非原始或非blittable数据.

为什么它不再起作用了?

我现在如何获取托管对象的内存地址?

Ill*_*ack 52

您可以使用GCHandleType.Weak而不是Pinned.另一方面,还有另一种获取指向对象的指针的方法:

object o = new object();
TypedReference tr = __makeref(o);
IntPtr ptr = **(IntPtr**)(&tr);
Run Code Online (Sandbox Code Playgroud)

需要不安全的阻塞,非常非常危险,根本不应该使用.☺

2019编辑:由于这个答案已经获得了一些关注,我觉得解释代码实际做了什么是必要的,以便这是一个正确的答案.

首先,在C#中无法使用by-ref本地人的那一天,有一个无证的机制可以完成类似的事情 - __makeref.

object o = new object();
ref object r = ref o;
//roughly equivalent to
TypedReference tr = __makeref(o);
Run Code Online (Sandbox Code Playgroud)

TypedReference的一个重要区别是"通用"; 它可用于存储对任何类型变量的引用.访问此类引用需要指定其类型,例如__refvalue(tr, object),如果它不匹配,则抛出异常.

要实现类型检查,TypedReference必须有两个字段,一个字段包含变量的实际地址,另一个字段具有指向其类型表示的指针.恰好是地址是第一个字段.

因此,__makeref首先使用获取对变量的引用o.转换(IntPtr**)(&tr)将结构视为一个数组(通过指针表示)IntPtr*(指向通用指针类型的指针),通过指向它的指针访问.首先取消引用指针以获得第一个字段,然后再次取消引用指针以获得实际存储在变量中的值o- 指向对象本身的指针.

但是,自2012年以来,我提出了一个更好,更安全的解决方案:

public static class ReferenceHelpers
{
    public static readonly Action<object, Action<IntPtr>> GetPinnedPtr;

    static ReferenceHelpers()
    {
        var dyn = new DynamicMethod("GetPinnedPtr", typeof(void), new[] { typeof(object), typeof(Action<IntPtr>) }, typeof(ReferenceHelpers).Module);
        var il = dyn.GetILGenerator();
        il.DeclareLocal(typeof(object), true);
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Stloc_0);
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Ldloc_0);
        il.Emit(OpCodes.Conv_I);
        il.Emit(OpCodes.Call, typeof(Action<IntPtr>).GetMethod("Invoke"));
        il.Emit(OpCodes.Ret);
        GetPinnedPtr = (Action<object, Action<IntPtr>>)dyn.CreateDelegate(typeof(Action<object, Action<IntPtr>>));
    }
}
Run Code Online (Sandbox Code Playgroud)

这将创建一个动态方法,首先固定对象(因此其存储不会在托管堆中移动),然后执行接收其地址的委托.在执行委托期间,对象仍然被固定,因此可以通过指针进行操作安全:

object o = new object();
ReferenceHelpers.GetPinnedPtr(o, ptr => Console.WriteLine(Marshal.ReadIntPtr(ptr) == typeof(object).TypeHandle.Value)); //the first pointer in the managed object header in .NET points to its run-time type info
Run Code Online (Sandbox Code Playgroud)

这是固定对象的最简单方法,因为GCHandle要求类型为blittable以便固定它.它的优点是不使用实现细节,未记录的关键字和内存黑客.

  • 我发现这只在一个场景中很有用:当我即将启动进程的自我转储时,在此之前,我想要将一堆内存地址打印到托管对象,以便稍后分析转储文件. (5认同)

SLa*_*aks 20

您应该调用GetHashCode(),而不是此代码,它将为每个实例返回一个(希望是)唯一值.

您也可以使用ObjectIDGenerator该类,该类保证是唯一的.

  • GetHashCode不是唯一的.对象id生成器id是唯一的,但它可以防止收集对象. (33认同)
  • @Eric:1)出于实用目的,我认为(但从未检查过)它已经足够好了。2)是否有记录在任何地方?查了一下来源,确实如此。 (3认同)
  • Re(1)好,实际取决于要解决的问题; 仅有9300次哈希后,碰撞的概率> 1%.Re(2)它没有明确记录,但正如您所注意到的,如果您使用反射器来拆分实现,您将看到它所做的只是将对象粘贴到哈希表中.只要该哈希表是活动的,对象也是如此.文档意味着只要序列化操作正在运行,id生成器应该保持活动状态. (3认同)
  • 对象ID生成器+1,我不知道它存在.散列不适用于需要缓存的大量对象,这不是我第一次遇到Duplicate Hashkey错误.不确定@EricLippert评论是否意味着碰撞也可以与ID生成器一起出现. (2认同)
  • 如果重写Equals,则还必须覆盖GetHashCode - 有时您还需要基于引用的哈希而不是基于相等的哈希.(即同一个对象的两个副本,在这个实例中应该给出两个不同的哈希值.) (2认同)

Ron*_*rby 12

如果您不是真的需要内存地址,而是一些唯一标识托管对象的方法,那么有一个更好的解决方案:

using System.Runtime.CompilerServices;

public static class Extensions
{
    private static readonly ConditionalWeakTable<object, RefId> _ids = new ConditionalWeakTable<object, RefId>();

    public static Guid GetRefId<T>(this T obj) where T: class
    {
        if (obj == null)
            return default(Guid);

        return _ids.GetOrCreateValue(obj).Id;
    }

    private class RefId
    {
        public Guid Id { get; } = Guid.NewGuid();
    }
}
Run Code Online (Sandbox Code Playgroud)

这是线程安全的并且在内部使用弱引用,因此您不会有内存泄漏.

您可以使用任何您喜欢的密钥生成方式.我在Guid.NewGuid()这里使用,因为它简单且线程安全.

更新

我继续创建了一个Nuget包Overby.Extensions.Attachments,其中包含一些扩展方法,用于将对象附加到其他对象.有一个扩展称为GetReferenceId()有效地执行此答案中的代码所显示的内容.


小智 6

这是我想出的一种简单方法,它不涉及不安全代码或固定对象。也可以反向工作(来自地址的对象):

public static class AddressHelper
{
    private static object mutualObject;
    private static ObjectReinterpreter reinterpreter;

    static AddressHelper()
    {
        AddressHelper.mutualObject = new object();
        AddressHelper.reinterpreter = new ObjectReinterpreter();
        AddressHelper.reinterpreter.AsObject = new ObjectWrapper();
    }

    public static IntPtr GetAddress(object obj)
    {
        lock (AddressHelper.mutualObject)
        {
            AddressHelper.reinterpreter.AsObject.Object = obj;
            IntPtr address = AddressHelper.reinterpreter.AsIntPtr.Value;
            AddressHelper.reinterpreter.AsObject.Object = null;
            return address;
        }
    }

    public static T GetInstance<T>(IntPtr address)
    {
        lock (AddressHelper.mutualObject)
        {
            AddressHelper.reinterpreter.AsIntPtr.Value = address;
            return (T)AddressHelper.reinterpreter.AsObject.Object;
        }
    }

    // I bet you thought C# was type-safe.
    [StructLayout(LayoutKind.Explicit)]
    private struct ObjectReinterpreter
    {
        [FieldOffset(0)] public ObjectWrapper AsObject;
        [FieldOffset(0)] public IntPtrWrapper AsIntPtr;
    }

    private class ObjectWrapper
    {
        public object Object;
    }

    private class IntPtrWrapper
    {
        public IntPtr Value;
    }
}
Run Code Online (Sandbox Code Playgroud)


Jim*_*hel 5

释放该句柄时,垃圾收集器可以自由移动固定的内存.如果你有一个指向应该被固定的内存的指针,并且你取消固定那个内存,那么所有的赌注都会被关闭.这在3.5中完全有效可能只是运气.JIT编译器和4.0的运行时可能在对象生命周期分析方面做得更好.

如果你真的想这样做,你可以使用a try/finally来阻止对象在你使用它之前被取消固定:

public static string Get(object a)
{
    GCHandle handle = GCHandle.Alloc(a, GCHandleType.Pinned);
    try
    {
        IntPtr pointer = GCHandle.ToIntPtr(handle);
        return "0x" + pointer.ToString("X");
    }
    finally
    {
        handle.Free();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这是执行失败的"GCHandle.Alloc(a,GCHandleType.Pinned)". (3认同)

Wil*_*zuk 0

切换分配类型:

GCHandle handle = GCHandle.Alloc(a, GCHandleType.Normal);
Run Code Online (Sandbox Code Playgroud)