在Swift中打印变量的内存地址

240

在新的Swift语言中,是否有任何方式可以模拟Objective-C中的[NSString stringWithFormat:@"%p", myVar]

例如:

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

2
[NSString stringWithFormat:@"%p", myVar] 中,myVar 必须是一个指针。在您的 Swift 代码中,str 不是一个指针,因此该比较不适用。 - user102008
8
值得注意的是,在我写这篇文章时,上述两条评论是不正确的。 - Ky -
15个回答

342

注意:此处仅适用于参考类型。

Swift 4/5:

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

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


Swift 3.1:

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

打印someVar的内存地址。



4
Xcode 8.2.1 的自动更正功能提示我现在应该使用 print(Unmanaged<AnyObject>.passUnretained(someVar as AnyObject).toOpaque()) - Jeff
3
只有当someVar是一个对象时,这个说法才是正确的。如果someVar是值类型,比如结构体,那么每次执行时得到的地址都会不同。 - OutOnAWeekend
5
你说得对,很可能是因为结构体在作为参数传递时被复制了。使用Unmanaged可以这样实现:print(Unmanaged<AnyObject>.fromOpaque(&myStruct).toOpaque()) - nyg
3
从 Swift 4 开始,我能够使用以下咒语将其打印出来 - Unmanaged.passUnretained(someVar).toOpaque()(不需要泛型规范)。 - Ying
4
Swift 4 的答案很完美。如果您还想获得字符串表示形式而不进行打印,我建议在末尾添加 debugDescription - Patrick
显示剩余4条评论

135

Swift 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

Swift 3

使用withUnsafePointer来适用于 Swift 3:

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

2
unsafeAddressOf() 只适用于类类型(正如 @Nick 在下面指出的那样)。因此,只有在导入 Foundation 并将 String 桥接到 NSString 时,此答案才有效。在纯 Swift 中,String 是一个值类型,不能使用 unsafeAddressOf 来获取其地址(尝试会导致编译错误)。 - Stephen Schaub
30
如果我想打印不可变值的地址,该怎么用Swift 3?使用withUnsafePointer会导致cannot pass immutable value as inout argument错误。 - Alexander Vasenin
17
尽管它被接受并成为最高票答案,但仍会得到错误的结果。例如:print(self.description)打印出<MyViewController: 0x101c1d580>,我们将其用作参考。var mutableSelf = self; withUnsafePointer(to: &mutableSelf) { print(String(format: "%p", $0)) }打印出明显不同的地址0x16fde4028。有人能解释一下为什么吗? - Alexander Vasenin
4
顺便说一句,这段代码输出了预期的结果 0x101c1d580print(String(format: "%p", unsafeBitCast(self, to: Int.self))) - Alexander Vasenin
11
你的答案给了我一个线索,了解实际错误原因:UnsafePointer是一个结构体,因此为了打印它所指向的地址(而不是结构体本身),你需要打印 String(format: "%p", $0.pointee) - Alexander Vasenin
显示剩余6条评论

50

请注意,这个答案比较旧了,它描述的许多方法已经不再适用。特别是.core无法再被访问。

然而@drew的回答是正确且简洁的:

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

所以你的问题的答案是:

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

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

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

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

如果你打开XCode -> 调试工作流程 -> 查看内存,并转到字符串的内存地址,就可以看到字符串的原始数据。由于这是一个字符串字面量,所以它是二进制存储空间内的一个内存地址(不是栈或堆)。

但是,如果你执行

    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<CInt>),所以它接受一个指向CInt的CMutablePointer,你可以用&varname获得它。

该函数打印出0x7fff5fbfed98,在这个内存地址中,你将找到289(十六进制表示)。你可以用*intptr = 123456来更改它的内容。

现在,还有一些其他需要知道的事情。

在Swift中,String是一个原始类型,而不是一个对象。
CInt是映射到C int类型的Swift类型。
如果你想要一个对象的内存地址,你必须做一些不同的事情。
Swift有一些指针类型,在与C交互时可以使用,你可以在这里了解更多:Swift Pointer Types
此外,你可以通过探索它们的声明(cmd+click on the type)来理解如何将一种指针类型转换为另一种。

    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

是的,指针必须存在,但不仅仅是为了兼容性原因,正如您所提到的。操作系统通常使用指针。即使语言是高级语言,指针在任何时候都必须存在于操作系统引擎中的任何语言中。 - holex
我说“出于兼容性原因和运行时需要”,第二个语句总结了你所说的内容(我假设程序员知道并理解它,所以我用了很少的话)。 - LombaX
我假设这不再适用了? - aleclarson
是的。我将@Drew的正确答案编辑进去了。 - Rog
@IshaanSejwal 不,unsafeAddressOf不会创建一个新指针。它只是返回与对象关联的存储的内存地址,而不是其指针地址。 - LombaX
显示剩余2条评论

37

简述:

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
*/

(代码片段)


在Swift中,我们处理值类型(结构体)或引用类型(类)。当进行以下操作时:

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

在地址X处分配了一些内存,这个地址上我们会发现值为42。执行&n将创建一个指向地址X的指针,因此&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)。

在这两种情况下,使用&n&c将给我们地址X。对于值类型来说,这正是我们想要的,但对于引用类型来说则不是。

执行以下操作时:

let referencePointer = UnsafeMutablePointer<C>(&c)

我们在引用上创建一个指针,即指向地址X的指针。使用withUnsafePointer(&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个不同的地址。 - user1046037
@user1046037 请查看:https://pastebin.com/mpd3ujw2。显然,所有空结构体指向同一内存地址。但是,当我们想要将指针存储在变量中时,它会创建一个副本(或结构体的副本?)... - nyg
这很有趣,但是当打印两次时,它会打印不同的内存地址。上面的答案也是如此。 - user1046037
@user1046037,我不确定我理解你的意思,你有一些代码吗?(我总是得到相同的内存地址) - nyg
谢谢您的回答!是否可以在通用结构(例如struct LinkedList<Value>)中使用MemoryAddress - derpoliuk
显示剩余3条评论

30

Swift 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
@Padraig 谢谢,我已经使用你的代码进行了更新。不幸的是,它现在需要一个 AnyObject 参数。我更喜欢将输入类型设为 Any - neoneye
1
为什么不是 AnyObject 的 Any 会有一个指针? - kocodude

23

获取对象的(堆)地址

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", &o][NSString stringWithFormat:@"%p", &i]s是结构体。因此,如果将s分配给另一个变量s2,则该值将被复制,并且s2的返回地址将不同。

它如何组合(指针回顾)

与Objective-C一样,o有两个不同的关联地址。第一个是对象的位置,第二个是对对象的引用(或指针)的位置。
是的,这意味着地址0x7fff5fbfe658的内容是数字0x6100000011d0,正如调试器所告诉我们的那样:
(lldb) x/g 0x7fff5fbfe658
0x7fff5fbfe658: 0x00006100000011d0

除了字符串是结构体之外,内部基本上与(Objective-C)相同。
(截至Xcode 6.3)

嗯。获取对象属性的堆栈地址并不一致。有什么想法吗?查看这个代码片段! - aleclarson
对象的属性位于堆上,而不是栈上。当您将类实例的属性作为UnsafePointer传递时,Swift实际上首先复制该值,然后您会得到该副本的地址。我怀疑这是为了防止C代码绕过对象的接口并导致不一致的状态。我不知道是否有办法解决这个问题。 - nschum
这是我在苹果开发者论坛上创建的一个帖子,里面有一些很好的回复。 - aleclarson

22

引用类型:

  • 获取引用类型的内存地址是有意义的,因为它代表身份。
  • === 身份运算符用于检查 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

值类型:

  • 需要获取值类型的内存地址并不是非常重要(因为它是一个值),更加重要的是值的相等性。

18

只需使用此代码:

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

如果编译器抱怨 "MyClass?" 不符合 CVarArg,你可以使用 extension Optional : CVarArg { }。否则,这似乎会打印地址,而不会像其他答案那样带有所有的 "unsafe" 疯狂行为。 - Devin Lane
2
需要在CVarArg上实现var _cVarArgEncoding: [Int]。不清楚应该如何实现。 - Yuchen

13

在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)
    }

1
挽救了我的一天。应该有一个徽章“最有用的最近s.o.获取”。 - Anton Tropashko
确实,这是唯一一个打印出我的 self?.array 的方法。 - Rostyslav Druzhchenko

13

如果你只是想在调试器中查看它而不对其进行其他任何操作,那么实际上没有必要获取 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...

1
请在给出任何负评时附上一条评论,解释为什么这是一个不好的解决方案,这样我和其他来到这里的人就知道原因了 :) - Ky -
1
简单而整洁! - badhanganesh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接