【问题标题】:Printing a variable memory address in swift快速打印可变内存地址
【发布时间】:2014-07-26 08:53:48
【问题描述】:

是否可以在新的 Swift 语言中模拟来自 Objective-C 的 [NSString stringWithFormat:@"%p", myVar]

例如:

let str = "A String"
println(" str value \(str) has address: ?")

【问题讨论】:

  • [NSString stringWithFormat:@"%p", myVar] 中,myVar 必须是指针。在您的 Swift 代码中,str 不是指针。所以比较不适用。
  • 值得注意的是,至少在我打字的时候,上面两个cmets是不正确的。

标签: swift debugging memory-address


【解决方案1】:

注意:这是针对引用类型的。

斯威夫特 4/5:

print(Unmanaged.passUnretained(someVar).toOpaque())

打印 someVar 的内存地址。 (感谢@Ying)


斯威夫特 3.1:

print(Unmanaged<AnyObject>.passUnretained(someVar as AnyObject).toOpaque())

打印 someVar 的内存地址。


【讨论】:

  • Xcode 8.2.1 自动更正告诉我现在是print(Unmanaged&lt;AnyObject&gt;.passUnretained(someVar as AnyObject).toOpaque())
  • 只有当 someVar 是一个对象时这不是真的。如果 someVar 恰好是 struct 之类的值类型,那么每次执行时都会给出不同的地址。
  • @OutOnAWeekend 你是对的,很可能是因为结构在作为参数传递时被复制。使用Unmanaged 可以这样做:print(Unmanaged&lt;AnyObject&gt;.fromOpaque(&amp;myStruct).toOpaque())
  • 从 Swift 4 开始,我也可以打印这个咒语 - Unmanaged.passUnretained(someVar).toOpaque()(不需要通用规范)
  • Swift 4 的答案是完美的。如果您还想在不打印的情况下获取字符串表示形式,我建议在其末尾添加 debugDescription
【解决方案2】:

斯威夫特 5

extension String {
    static func pointer(_ object: AnyObject?) -> String {
        guard let object = object else { return "nil" }
        let opaque: UnsafeMutableRawPointer = Unmanaged.passUnretained(object).toOpaque()
        return String(describing: opaque)
    }
}

用法:

print("FileManager.default: \(String.pointer(FileManager.default))")
// FileManager.default: 0x00007fff5c287698

print("nil: \(String.pointer(nil))")
// nil: nil

【讨论】:

  • 这个答案不正确。如果您创建同一个类的两个实例,它将为两个实例返回相同的内存地址。 Unmanaged.passUnretained(myObject).toOpaque() 可以正常工作。
  • @Padraig 谢谢,我已经用你的代码更新了。可悲的是,它现在需要一个 AnyObject 参数。我更喜欢 Any 作为输入类型。
  • 为什么非 AnyObject 的 Any 会有指针?
【解决方案3】:

在 Swift4 中关于数组:

    let array1 = [1,2,3]
    let array2 = array1
    array1.withUnsafeBufferPointer { (point) in
        print(point) // UnsafeBufferPointer(start: 0x00006000004681e0, count: 3)
    }
    array2.withUnsafeBufferPointer { (point) in
        print(point) // UnsafeBufferPointer(start: 0x00006000004681e0, count: 3)
    }

【讨论】:

  • 拯救了我的一天。应该有一个徽章“最近最有用的s.o.获取”
  • 确实,这是打印我的self?.array 的唯一方法。
【解决方案4】:

就用这个吧:

print(String(format: "%p", object))

【讨论】:

  • 如果您收到关于“MyClass?”的编译器投诉不符合 CVarArg,您可以使用extension Optional : CVarArg { }。否则,这似乎打印的地址没有其他答案的所有“不安全”精神错乱。
  • 它需要在CVarArg 上实现var _cVarArgEncoding: [Int]。不清楚应该如何实施。
【解决方案5】:

TL;DR

struct MemoryAddress<T>: CustomStringConvertible {

    let intValue: Int

    var description: String {
        let length = 2 + 2 * MemoryLayout<UnsafeRawPointer>.size
        return String(format: "%0\(length)p", intValue)
    }

    // for structures
    init(of structPointer: UnsafePointer<T>) {
        intValue = Int(bitPattern: structPointer)
    }
}

extension MemoryAddress where T: AnyObject {

    // for classes
    init(of classInstance: T) {
        intValue = unsafeBitCast(classInstance, to: Int.self)
        // or      Int(bitPattern: Unmanaged<T>.passUnretained(classInstance).toOpaque())
    }
}

/* Testing */

class MyClass { let foo = 42 }
var classInstance = MyClass()
let classInstanceAddress = MemoryAddress(of: classInstance) // and not &classInstance
print(String(format: "%018p", classInstanceAddress.intValue))
print(classInstanceAddress)

struct MyStruct { let foo = 1 } // using empty struct gives weird results (see comments)
var structInstance = MyStruct()
let structInstanceAddress = MemoryAddress(of: &structInstance)
print(String(format: "%018p", structInstanceAddress.intValue))
print(structInstanceAddress)

/* output
0x0000000101009b40
0x0000000101009b40
0x00000001005e3000
0x00000001005e3000
*/

(Gist)


在 Swift 中,我们处理值类型(结构)或引用类型(类)。做的时候:

let n = 42 // Int is a structure, i.e. value type

在地址 X 分配了一些内存,在这个地址我们会找到值 42。执行&amp;n 会创建一个指向地址 X 的指针,因此&amp;n 会告诉我们n 的位置。

(lldb) frame variable -L n
0x00000001005e2e08: (Int) n = 42
(lldb) memory read -c 8 0x00000001005e2e08
0x1005e2e08: 2a 00 00 00 00 00 00 00 // 0x2a is 42

做的时候:

class C { var foo = 42, bar = 84 }
var c = C()

内存分配在两个地方:

  • 在类实例数据所在的地址 Y 和
  • 类实例引用所在的地址 X。

如上所述,类是引用类型:所以c 的值位于地址 X,我们将在该地址找到 Y 的值。在地址 Y + 16 我们将找到 foo 和地址 Y + 24 我们将找到 bar(在 + 0 和 + 8 处,我们将找到类型数据和引用计数,我无法告诉您更多关于此的信息......)。

(lldb) frame variable c // gives us address Y
(testmem.C) c = 0x0000000101a08f90 (foo = 42, bar = 84)
(lldb) memory read 0x0000000101a08f90 // reading memory at address Y
0x101a08f90: e0 65 5b 00 01 00 00 00 02 00 00 00 00 00 00 00
0x101a08fa0: 2a 00 00 00 00 00 00 00 54 00 00 00 00 00 00 00

0x2a 是 42 (foo),0x54 是 84 (bar)。

在这两种情况下,使用&amp;n&amp;c 都会给我们地址X。对于值类型,这就是我们想要的,但不是引用类型。

做的时候:

let referencePointer = UnsafeMutablePointer<C>(&c)

我们在引用上创建一个指针,即指向地址 X 的指针。使用 withUnsafePointer(&amp;c) {} 时也是如此。

(lldb) frame variable referencePointer
(UnsafeMutablePointer<testmem.C>) referencePointer = 0x00000001005e2e00 // address X
(lldb) memory read -c 8 0x00000001005e2e00 // read memory at address X
0x1005e2e00: 20 ec 92 01 01 00 00 00 // contains address Y, consistent with result below:
(lldb) frame variable c
(testmem.C) c = 0x000000010192ec20 (foo = 42, bar = 84)

现在我们对底层发生的事情有了更好的理解,并且我们现在在地址 X 处可以找到地址 Y(这是我们想要的),我们可以执行以下操作来获取它:

let addressY = unsafeBitCast(c, to: Int.self)

验证:

(lldb) frame variable addressY -f hex
(Int) addressY = 0x0000000101b2fd20
(lldb) frame variable c
(testmem.C) c = 0x0000000101b2fd20 (foo = 42, bar = 84)

还有其他方法可以做到这一点:

let addressY1 = Int(bitPattern: Unmanaged.passUnretained(c).toOpaque())
let addressY2 = withUnsafeMutableBytes(of: &c) { $0.load(as: Int.self) }

toOpaque()实际上调用了unsafeBitCast(c, to: UnsafeMutableRawPointer.self)

我希望这会有所帮助......它对我有用 ?。

【讨论】:

  • 刚刚注意到,在尝试通过 MemoryLocation 的 2 个不同实例打印同一结构的地址时,会产生 2 个不同的地址。
  • @user1046037 谢谢,对类初始化进行了更改。我也得到两个不同的地址,但只有当我使用空结构时。使用空结构总是给我奇怪的结果。我的猜测是编译器做了一些优化......
  • @user1046037 检查:pastebin.com/mpd3ujw2。显然,所有空结构都指向同一个内存地址。但是,当我们想将指针存储在变量中时,它将创建它的副本(或结构?)...
  • 这很有趣,但是当打印两次时,它会打印不同的内存地址。上面的答案也是如此。
  • @user1046037 我不确定我明白你的意思,你有一些代码吗? (我总是得到相同的内存地址)
【解决方案6】:

参考类型:

  • 获取引用类型的内存地址是有意义的,因为它代表身份。
  • === 标识运算符用于检查 2 个对象指向同一个引用。
  • 使用ObjectIdentifier获取内存地址

代码:

class C {}

let c1 = C()
let c2 = c1

//Option 1:
print("c1 address: \(Unmanaged.passUnretained(c1).toOpaque())") 

//Option 2:
let o1 = ObjectIdentifier(c1)
let o2 = ObjectIdentifier(c2)

print("o1 -> c1 = \(o1)")
print("o2 -> c2 = \(o2)")

if o1 == o2 {
    print("c1 = c2")
} else {
    print("c1 != c2")
}

//Output:
//c1 address: 0x000060c000005b10
//o1 -> c1 = ObjectIdentifier(0x000060c000005b10)
//o2 -> c2 = ObjectIdentifier(0x000060c000005b10)
//c1 = c2

值类型:

  • 获取值类型的内存地址的需要并不重要(因为它是一个值),重点将更多地放在值的相等性上。

【讨论】:

    【解决方案7】:

    这当然不是最快或最安全的方法。但它对我有用。这将允许任何 nsobject 子类采用此属性。

    public extension NSObject {
        public var memoryAddress : String? {
            let str = "\(self.self)".components(separatedBy: ": ")
            guard str.count > 1 else { return nil }
            return str[1].replacingOccurrences(of: ">", with: "")            
        }
    }
    
    //usage 
    let foo : String! = "hello"
    Swift.print(foo.memoryAddress) // prints 0x100f12980
    

    【讨论】:

    • 感谢杰夫的反馈,我会更新答案
    • 没关系!这是我的错!您的代码在纯 Swift 类中运行良好。抱歉弄错了。
    【解决方案8】:

    我在 Swift 3 上的解决方案

    extension MyClass: CustomStringConvertible {
        var description: String {
            return "<\(type(of: self)): 0x\(String(unsafeBitCast(self, to: Int.self), radix: 16, uppercase: false))>"
        }
    }
    

    此代码创建描述,如默认描述 &lt;MyClass: 0x610000223340&gt;

    【讨论】:

      【解决方案9】:

      这是为 Swift 3 准备的。

      像@CharlieMonroe 一样,我想以整数形式获取地址。具体来说,我希望 Thread 对象的地址用作诊断日志记录模块中的线程 ID,以用于没有可用线程名称的情况。

      根据 Charlie Monroe 的代码,这是我迄今为止提出的。但请注意,我对 Swift 很陌生,这可能不正确......

        // Convert the memory address of the current Thread object into an Int for use as a thread ID
        let objPtr = Unmanaged.passUnretained(Thread.current).toOpaque()
        let onePtr = UnsafeMutableRawPointer(bitPattern: 1)!  // 1 used instead of 0 to avoid crash
        let rawAddress : Int64 = onePtr.distance(to: objPtr) + 1  // This may include some high-order bits
        let address = rawAddress % (256 * 1024 * 1024 * 1024)  // Remove high-order bits
      

      最后一条语句在那里,因为没有它我会得到像 0x60000007DB3F 这样的地址。最后一条语句中的模运算将其转换为 0x7DB3F。

      【讨论】:

        【解决方案10】:

        如果您只想在调试器中看到它而不用它做任何其他事情,则实际上不需要获取Int 指针。要获取内存中对象地址的字符串表示形式,只需使用以下内容:

        public extension NSObject { // Extension syntax is cleaner for my use. If your needs stem outside NSObject, you may change the extension's target or place the logic in a global function
            public var pointerString: String {
                return String(format: "%p", self)
            }
        }
        

        示例用法:

        print(self.pointerString, "Doing something...")
        // Prints like: 0x7fd190d0f270 Doing something...
        

        另外,请记住,您可以简单地打印一个对象,而无需覆盖其description,它会在更具描述性(如果经常隐晦)的文本旁边显示其指针地址。

        print(self, "Doing something else...")
        // Prints like: <MyModule.MyClass: 0x7fd190d0f270> Doing something else...
        // Sometimes like: <_TtCC14__lldb_expr_668MyModule7MyClass: 0x7fd190d0f270> Doing something else...
        

        【讨论】:

        • 请在任何不赞成票的同时附上解释原因的评论,这样我和其他来到这里的人都知道为什么这是一个糟糕的解决方案:)
        • 简单整洁!
        【解决方案11】:

        斯威夫特 2

        现在这是标准库的一部分:unsafeAddressOf

        /// Return an UnsafePointer to the storage used for `object`.  There's
        /// not much you can do with this other than use it to identify the
        /// object
        

        斯威夫特 3

        对于 Swift 3,使用 withUnsafePointer:

        var str = "A String"
        withUnsafePointer(to: &str) {
            print(" str value \(str) has address: \($0)")
        }
        

        【讨论】:

        • unsafeAddressOf() 仅适用于类类型(正如@Nick 在下面指出的那样)。因此,此答案仅在导入 Foundation 并将 String 桥接到 NSString 时才有效。在普通的 Swift 中,String 是一个值类型,不能使用 unsafeAddressOf 来获取它的地址(尝试会导致编译错误)。
        • 如果我想用 Swift 3 打印 immutable 值的地址怎么办? withUnsafePointer 导致 cannot pass immutable value as inout argument 错误。
        • 即使它是被接受且投票率最高的答案,它仍然给出错误的结果。示例:print(self.description) 打印 &lt;MyViewController: 0x101c1d580&gt;,我们将其用作参考。 var mutableSelf = self; withUnsafePointer(to: &amp;mutableSelf) { print(String(format: "%p", $0)) } 打印 0x16fde4028 这显然是不同的地址。谁能解释一下为什么?
        • 顺便说一句,这会按预期打印0x101c1d580print(String(format: "%p", unsafeBitCast(self, to: Int.self)))
        • @nyg 你的回答给了我一个关于实际错误原因的线索:UnsafePointer 是一个结构,所以为了打印 它指向的地址(而不是struct 本身)你必须打印 String(format: "%p", $0.pointee)!
        【解决方案12】:

        @Drew 提供的答案只能用于类类型。
        @nschum 提供的答案只能针对 struct 类型。

        但是,如果您使用第二种方法来获取具有值类型元素的数组的地址。 Swift 将复制整个数组,因为在 Swift 中数组是写时复制的,并且 Swift 无法确保一旦将控制权交给 C/C++(这是通过使用 &amp; 来获取地址来触发的),它就会以这种方式运行。如果你改用第一种方法,它会自动将Array 转换为NSArray,这肯定是我们不想要的。

        所以我找到的最简单统一的方法是使用lldb指令frame variable -L yourVariableName

        或者你可以结合他们的答案:

        func address(o: UnsafePointer<Void>) {
            let addr = unsafeBitCast(o, Int.self)
            print(NSString(format: "%p", addr))
        }
        
        func address<T: AnyObject>(o: T) -> String{
            let addr = unsafeBitCast(o, Int.self)
            return NSString(format: "%p", addr) as String
        }
        

        【讨论】:

          【解决方案13】:

          其他答案都很好,虽然我正在寻找一种将指针地址作为整数获取的方法:

          let ptr = unsafeAddressOf(obj)
          let nullPtr = UnsafePointer<Void>(bitPattern: 0)
          
          /// This gets the address of pointer
          let address = nullPtr.distanceTo(ptr) // This is Int
          

          只是一点点跟进。

          【讨论】:

          • 有关 Swift 3 的此答案的版本,请参见下文。
          【解决方案14】:

          请注意,这个答案已经很老了。它描述的许多方法不再有效。特别是.core 不能再访问了。

          不过@drew 的回答是正确而简单的:

          现在这是标准库的一部分:unsafeAddressOf。

          所以你的问题的答案是:

          println(" str value \(str) has address: \(unsafeAddressOf(str))")
          

          这是被标记为正确的原始答案(为了后代/礼貌):

          Swift“隐藏”了指针,但它们仍然存在于底层。 (因为运行时需要它,并且出于与 Objc 和 C 的兼容性原因)

          然而,有几件事要知道,但首先如何打印 Swift String 的内存地址?

              var aString : String = "THIS IS A STRING"
              NSLog("%p", aString.core._baseAddress)  // _baseAddress is a COpaquePointer
             // example printed address 0x100006db0
          

          这会打印字符串的内存地址,如果你打开 XCode -> Debug Workflow -> View Memory 并转到打印的地址,你会看到字符串的原始数据。 由于这是一个字符串字面量,因此这是二进制存储中的内存地址(不是堆栈或堆)。

          但是,如果你这样做了

              var aString : String = "THIS IS A STRING" + "This is another String"
              NSLog("%p", aString.core._baseAddress)
          
              // example printed address 0x103f30020
          

          这将在堆栈上,因为字符串是在运行时创建的

          注意:.core._baseAddress 没有记录,我在变量检查器中发现它,它可能在未来被隐藏

          _baseAddress 并非在所有类型上都可用,这里是另一个带有 CInt 的示例

              var testNumber : CInt = 289
              takesInt(&testNumber)
          

          takesInt 是这样的 C 辅助函数

          void takesInt(int *intptr)
          {
              printf("%p", intptr);
          }
          

          在 Swift 端,这个函数是takesInt(intptr: CMutablePointer&lt;CInt&gt;),所以它需要一个 CMutablePointer 到一个 CInt,你可以用 &varname 来获取它

          函数打印0x7fff5fbfed98,在这个内存地址你会发现289(十六进制)。您可以使用*intptr = 123456更改其内容

          现在,还有一些事情要知道。

          字符串,在 swift 中,是一个原始类型,而不是一个对象。
          CInt 是映射到 C int 类型的 Swift 类型。
          如果你想要一个对象的内存地址,你必须做一些不同的事情。
          Swift 有一些指针类型可以在与 C 交互时使用,你可以在这里阅读它们:Swift Pointer Types
          此外,您可以了解更多关于他们探索他们的声明(cmd+点击类型),了解如何将指针类型转换为另一种类型

              var aString : NSString = "This is a string"  // create an NSString
              var anUnmanaged = Unmanaged<NSString>.passUnretained(aString)   // take an unmanaged pointer
              var opaque : COpaquePointer = anUnmanaged.toOpaque()   // convert it to a COpaquePointer
              var mut : CMutablePointer = &opaque   // this is a CMutablePointer<COpaquePointer>
          
              printptr(mut)   // pass the pointer to an helper function written in C
          

          printptr 是我创建的一个 C 辅助函数,用这个实现

          void printptr(void ** ptr)
          {
              printf("%p", *ptr);
          }
          

          再次,打印地址的示例:0x6000000530b0,如果您通过内存检查器,您将找到您的 NSString

          在 Swift 中你可以用指针做一件事(这甚至可以用 inout 参数来做)

              func playWithPointer (stringa :AutoreleasingUnsafePointer<NSString>) 
              {
                  stringa.memory = "String Updated";
              }
          
              var testString : NSString = "test string"
              println(testString)
              playWithPointer(&testString)
              println(testString)
          

          或者,与 Objc / c 交互

          // objc side
          + (void)writeString:(void **)var
          {
              NSMutableString *aString = [[NSMutableString alloc] initWithFormat:@"pippo %@", @"pluto"];
              *var = (void *)CFBridgingRetain(aString);   // Retain!
          }
          
          // swift side
          var opaque = COpaquePointer.null()   // create a new opaque pointer pointing to null
          TestClass.writeString(&opaque)
          var string = Unmanaged<NSString>.fromOpaque(opaque).takeRetainedValue()
          println(string)
          // this prints pippo pluto
          

          【讨论】:

          • 是的,指针必须存在,但不仅仅是因为您提到的兼容性原因。操作系统通常使用的指针。即使语言是高级语言,指针也必须在任何时间以任何语言存在于操作系统引擎中。
          • 我说“出于兼容性原因并且因为运行时需要它”:-) 第二个语句概括了您所说的内容(我假设程序员知道并理解它,所以我花了几句话)
          • 我认为这不再适用?
          • 是的。我将@Drew 的正确答案编辑到其中。
          • @LombaX 我们只能打印引用类型的地址?我不能打印变量的地址吗?
          【解决方案15】:

          获取对象的(堆)地址

          func address<T: AnyObject>(o: T) -> Int {
              return unsafeBitCast(o, Int.self)
          }
          
          class Test {}
          var o = Test()
          println(NSString(format: "%p", address(o))) // -> 0x7fd5c8700970
          

          编辑: Swift 1.2 现在包含一个类似的函数,称为unsafeAddressOf。)

          在 Objective-C 中,这将是 [NSString stringWithFormat:@"%p", o]

          o 是对实例的引用。因此如果将o 分配给另一个变量o2,则o2 的返回地址将是相同的。

          这不适用于结构(包括String)和原始类型(如Int),因为它们直接存在于堆栈中。但是我们可以检索堆栈上的位置。

          获取结构、内置类型或对象引用的(堆栈)地址

          func address(o: UnsafePointer<Void>) -> Int {
              return unsafeBitCast(o, Int.self)
          }
          
          println(NSString(format: "%p", address(&o))) // -> 0x10de02ce0
          
          var s = "A String"
          println(NSString(format: "%p", address(&s))) // -> 0x10de02ce8
          
          var i = 55
          println(NSString(format: "%p", address(&i))) // -> 0x10de02d00
          

          在 Objective-C 中,这将是 [NSString stringWithFormat:@"%p", &amp;o][NSString stringWithFormat:@"%p", &amp;i]

          s 是结构体。因此,如果将s 分配给另一个变量s2,则该值将被复制并且s2 的返回地址将不同。

          如何组合在一起(指针回顾)

          就像在 Objective-C 中一样,有两个不同的地址与 o 相关联。第一个是对象的位置,第二个是对象的引用(或指针)的位置。

          是的,这意味着地址 0x7fff5fbfe658 的内容是调试器告诉我们的数字 0x6100000011d0:

          (lldb) x/g 0x7fff5fbfe658
          0x7fff5fbfe658: 0x00006100000011d0
          

          所以,除了字符串是结构之外,在内部这一切都与 (Objective-)C 中的一样。

          (当前为 Xcode 6.3)

          【讨论】:

          • 嗯。获取对象属性的堆栈地址并不一致。有任何想法吗? Check out this gist!
          • 对象的属性在堆上,而不是在栈上。当您将类实例的属性作为 UnsafePointer 传递时,Swift 实际上首先复制该值,然后您将获得副本的地址。我怀疑这是为了防止C代码绕过对象的接口并导致不一致的状态。不知道有没有办法解决。
          • 这里是a thread I created on the Apple Developer Forums。里面有一些不错的回复。
          猜你喜欢
          • 2020-01-09
          • 1970-01-01
          • 1970-01-01
          • 2016-10-10
          • 2013-04-30
          • 2017-02-21
          • 1970-01-01
          • 1970-01-01
          • 2019-07-30
          相关资源
          最近更新 更多