Imprimir una dirección de memoria variable en Swift


166

¿Hay alguna forma de simular [NSString stringWithFormat:@"%p", myVar], desde Objective-C, en el nuevo lenguaje Swift?

Por ejemplo:

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

2
En [NSString stringWithFormat:@"%p", myVar], myVardebe ser un puntero. En su código Swift, strno es un puntero. Entonces la comparación no se aplica.
user102008

55
Vale la pena señalar que, al menos cuando estoy escribiendo esto, los dos comentarios anteriores son incorrectos.
Ben Leggiero

Respuestas:


112

Swift 2

Esto es ahora parte de la biblioteca estándar: 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

Para Swift 3, use withUnsafePointer:

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

2
unsafeAddressOf () funciona solo para los tipos de clase (como @Nick señala a continuación). Por lo tanto, esta respuesta solo funciona si Foundation se importa y String se conecta a NSString. En Swift simple, String es un tipo de valor y unsafeAddressOf no se puede usar para tomar su dirección (el intento produce un error de compilación).
Stephen Schaub

29
¿Qué sucede si quiero imprimir una dirección de valor inmutable con Swift 3? withUnsafePointerda como resultado un cannot pass immutable value as inout argumenterror.
Alexander Vasenin

12
A pesar de que es una respuesta aceptada y mejor votada, aún da resultados incorrectos . Ejemplo: print(self.description)impresiones <MyViewController: 0x101c1d580>, lo usamos como referencia. var mutableSelf = self; withUnsafePointer(to: &mutableSelf) { print(String(format: "%p", $0)) }Impresiones 0x16fde4028que es claramente diferente dirección. ¿Alguien puede explicar por qué?
Alexander Vasenin

44
Por cierto, esto se imprime 0x101c1d580como se esperaba:print(String(format: "%p", unsafeBitCast(self, to: Int.self)))
Alexander Vasenin

8
@nyg Su respuesta me dio una pista sobre la causa del error real: UnsafePointeres una estructura, por lo que para imprimir la dirección a la que apunta (y no la estructura en sí) ¡tiene que imprimir String(format: "%p", $0.pointee)!
Alexander Vasenin

214

Nota: Esto es para tipos de referencia.

Swift 4/5:

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

Imprime la dirección de memoria de someVar. (gracias a @Ying)


Swift 3.1:

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

Imprime la dirección de memoria de someVar.



2
Xcode 8.2.1 autocorrección me dice que es ahoraprint(Unmanaged<AnyObject>.passUnretained(someVar as AnyObject).toOpaque())
Jeff

2
¿No sería esto cierto solo si someVar es un objeto? Si someVar es un tipo de valor, como una estructura, esto proporcionará una dirección diferente cada vez que se ejecute.
OutOnAWeekend

3
@OutOnAWeekend Tienes razón, probablemente porque la estructura se copia cuando se pasa como argumento. El uso Unmanagedque se puede hacer así: print(Unmanaged<AnyObject>.fromOpaque(&myStruct).toOpaque()).
nyg

1
A partir de Swift 4, pude conseguir que este encantamiento se imprimiera también Unmanaged.passUnretained(someVar).toOpaque()(sin necesidad de una especificación genérica)
Ying

2
La respuesta de Swift 4 es perfecta. Si también desea obtener la representación de cadena sin imprimir, le recomiendo agregarla debugDescriptional final.
Patrick

51

Tenga en cuenta que esta respuesta era bastante antigua. Muchos de los métodos que describe ya no funcionan. Específicamente .coreya no se puede acceder.

Sin embargo, la respuesta de @ drew es correcta y simple:

Esto ahora es parte de la biblioteca estándar: unsafeAddressOf.

Entonces la respuesta a sus preguntas es:

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

Aquí está la respuesta original que se marcó correcta (para posteridad / cortesía):

Los punteros rápidos "ocultan", pero todavía existen debajo del capó. (porque el tiempo de ejecución lo necesita, y por razones de compatibilidad con Objc y C)

Sin embargo, hay algunas cosas que debe saber, pero primero, ¿cómo imprimir la dirección de memoria de una cadena rápida?

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

Esto imprime la dirección de memoria de la cadena, si abre XCode -> Debug Workflow -> View Memory y va a la dirección impresa, verá los datos sin procesar de la cadena. Como se trata de un literal de cadena, se trata de una dirección de memoria dentro del almacenamiento del binario (no de pila o montón).

Sin embargo, si lo haces

    var aString : String = "THIS IS A STRING" + "This is another String"
    NSLog("%p", aString.core._baseAddress)

    // example printed address 0x103f30020

Esto estará en la pila, porque la cadena se crea en tiempo de ejecución

NOTA: .core._baseAddress no está documentado, lo encontré buscando en el inspector de variables y puede estar oculto en el futuro

_baseAddress no está disponible en todos los tipos, aquí otro ejemplo con un CInt

    var testNumber : CInt = 289
    takesInt(&testNumber)

¿Dónde takesIntestá una función auxiliar de C como esta?

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

En el lado de Swift, esta función es takesInt(intptr: CMutablePointer<CInt>), por lo que toma un CMutablePointer a un CInt, y puede obtenerlo con & varname

La función se imprime 0x7fff5fbfed98, y en esta dirección de memoria encontrará 289 (en notación hexadecimal). Puedes cambiar su contenido con*intptr = 123456

Ahora, algunas otras cosas para saber.

La cadena, en forma rápida, es un tipo primitivo, no un objeto.
CInt es un tipo Swift asignado al tipo C int.
Si desea la dirección de memoria de un objeto, debe hacer algo diferente.
Swift tiene algunos tipos de puntero que se pueden usar al interactuar con C, y puede leer sobre ellos aquí: Tipos de puntero Swift
Además, puede comprender más acerca de cómo explorar su declaración (cmd + clic en el tipo), para comprender cómo convertir un tipo de puntero a otro

    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 es una función auxiliar de C que creé, con esta implementación

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

Nuevamente, un ejemplo de la dirección impresa: 0x6000000530b0y si pasa por el inspector de memoria encontrará su NSString

Una cosa que puedes hacer con punteros en Swift (esto incluso se puede hacer con parámetros inout)

    func playWithPointer (stringa :AutoreleasingUnsafePointer<NSString>) 
    {
        stringa.memory = "String Updated";
    }

    var testString : NSString = "test string"
    println(testString)
    playWithPointer(&testString)
    println(testString)

O, interactuando con 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

Sí, el puntero tiene que salir, pero no solo por razones de compatibilidad, como lo mencionó. Los punteros utilizados habitualmente por el sistema operativo. incluso si el idioma es de alto nivel, los punteros deben existir en cualquier idioma en cualquier momento en el motor del sistema operativo.
holex

Dije "por razones de compatibilidad Y porque el tiempo de ejecución lo necesita" :-) la segunda declaración recapitula lo que está diciendo (supongo que un programador lo sabe y lo entiende, así que pasé algunas palabras)
LombaX

¿Supongo que esto ya no se aplica?
aleclarson

Si. Edité la respuesta correcta de @Drew en él.
Rog

@LombaX, ¿solo podemos imprimir la dirección del tipo de referencia? ¿No puedo imprimir la dirección de las variables?
AbhimanyuAryan

21

Para obtener la dirección (montón) de un objeto

func address<T: AnyObject>(o: T) -> Int {
    return unsafeBitCast(o, Int.self)
}

class Test {}
var o = Test()
println(NSString(format: "%p", address(o))) // -> 0x7fd5c8700970

( Editar: Swift 1.2 ahora incluye una función similar llamada unsafeAddressOf).

En Objective-C esto sería [NSString stringWithFormat:@"%p", o].

oes una referencia a la instancia. Entonces, si ose asigna a otra variable o2, la dirección devuelta o2será la misma.

Esto no se aplica a estructuras (incluidos String) y tipos primitivos (como Int), porque esos viven directamente en la pila. Pero podemos recuperar la ubicación en la pila.

Para obtener la dirección (pila) de una estructura, tipo incorporado o referencia de objeto

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

En Objective-C esto sería [NSString stringWithFormat:@"%p", &o]o [NSString stringWithFormat:@"%p", &i].

ses struct. Entonces, si sse asigna a otra variable s2, el valor se copiará y la dirección devuelta s2será diferente.

Cómo encaja (resumen del puntero)

Al igual que en Objective-C, hay dos direcciones diferentes asociadas con o. El primero es la ubicación del objeto, el segundo es la ubicación de la referencia (o puntero) al objeto.

Sí, esto significa que el contenido de la dirección 0x7fff5fbfe658 es el número 0x6100000011d0 como el depurador puede decirnos:

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

Entonces, excepto que las cadenas son estructuras, internamente todo esto funciona casi igual que en (Objective-) C.

(Actual a partir de Xcode 6.3)


Hmm Obtener la dirección de la pila de la propiedad de un objeto no es consistente. ¿Algunas ideas? ¡Mira esta esencia!
aleclarson

Las propiedades del objeto están en el montón, no en la pila. Cuando pasa la propiedad de una instancia de clase como UnsafePointer, Swift copia el valor primero y obtiene la dirección de la copia. Sospecho que eso es para evitar que el código C eluda la interfaz del objeto y cause un estado inconsistente. No sé si hay una forma de evitar eso.
nschum

Aquí hay un hilo que creé en los foros de desarrolladores de Apple . Algunas buenas respuestas allí.
aleclarson

20

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

( Esencia )


En Swift tratamos con tipos de valor (estructuras) o tipos de referencia (clases). Al hacer:

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

Se asigna algo de memoria en la dirección X, y en esta dirección encontraremos el valor 42. Hacer &ncrea un puntero que apunta a la dirección X, por lo tanto, &nnos dice dónde nestá ubicado.

(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

Al hacer:

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

La memoria se asigna en dos lugares:

  • en la dirección Y donde se encuentran los datos de la instancia de clase y
  • en la dirección X donde se encuentra la referencia de instancia de clase.

Como se dijo, las clases son tipos de referencia: por lo tanto, el valor de cse encuentra en la dirección X, donde encontraremos el valor de Y. Y en la dirección Y + 16 encontraremos fooy en la dirección Y + 24 encontraremos bar( en + 0 y + 8 encontraremos datos de tipo y recuentos de referencias, no puedo decirle mucho más sobre esto ...).

(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

0x2aes 42 (foo) y 0x54es 84 (bar).

En ambos casos, usar &no &cnos dará la dirección X. Para los tipos de valor, eso es lo que queremos, pero no para los tipos de referencia.

Al hacer:

let referencePointer = UnsafeMutablePointer<C>(&c)

Creamos un puntero en la referencia, es decir, un puntero que apunta a la dirección X. Lo mismo cuando se usa 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)

Ahora que tenemos una mejor comprensión de lo que sucede debajo del capó, y ahora que en la dirección X encontraremos la dirección Y (que es la que queremos), podemos hacer lo siguiente para obtenerla:

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

Verificando:

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

Hay otras formas de hacer esto:

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

toOpaque()En realidad llama unsafeBitCast(c, to: UnsafeMutableRawPointer.self).

Espero que esto haya ayudado ... lo hizo para mí 😆.


Acabo de notar que al intentar imprimir la dirección de la misma estructura a través de 2 instancias diferentes de MemoryLocationproduce 2 direcciones diferentes.
user1046037

@ user1046037 Gracias, realizó el cambio para la clase init. También obtengo dos direcciones diferentes, pero solo cuando uso una estructura vacía. Usar una estructura vacía siempre me da resultados extraños. Mi conjetura es que el compilador hace algunas optimizaciones ...
nyg

@ user1046037 Compruebe: pastebin.com/mpd3ujw2 . Aparentemente, todas las estructuras vacías apuntan a la misma dirección de memoria. Sin embargo, cuando queremos almacenar el puntero en una variable se creará una copia de la misma (o de la estructura?) ...
nyg

Eso es interesante, pero cuando se imprime dos veces imprime las diferentes direcciones de memoria. Igual es el caso con las respuestas anteriores también.
usuario1046037

@ user1046037 No estoy seguro de entender lo que quieres decir, ¿tienes algún código? (Siempre obtengo la misma dirección de memoria)
nyg

15

Tipos de referencia:

  • Tiene sentido obtener la dirección de memoria de un tipo de referencia, ya que representa la identidad.
  • === El operador de identidad se utiliza para comprobar que 2 objetos apuntan a la misma referencia.
  • Use ObjectIdentifierpara obtener la dirección de memoria

Código:

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

Tipos de valor:

  • La necesidad de obtener la dirección de memoria de un tipo de valor no tiene mucha importancia (ya que es un valor) y el énfasis estaría más en la igualdad del valor.

12

Solo usa esto:

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

Si recibe una queja del compilador sobre " MyClass?" no se ajusta a CVarArg, puede hacerlo extension Optional : CVarArg { }. De lo contrario, esto parece imprimir direcciones sin toda la locura "insegura" de las otras respuestas.
Devin Lane, el

Requiere la implementación de var _cVarArgEncoding: [Int]on CVarArg. No está claro cómo se debe implementar eso.
Yuchen Zhong

10

Si solo quiere ver esto en el depurador y no hacer nada más con él, no hay necesidad de obtener el Intpuntero. Para obtener la representación de cadena de la dirección de un objeto en la memoria, simplemente use algo como esto:

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

Ejemplo de uso:

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

Además, recuerde que simplemente puede imprimir un objeto sin anularlo description, y mostrará su dirección de puntero junto con un texto más descriptivo (aunque a menudo críptico).

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

1
Acompañe cualquier voto negativo con un comentario que explique por qué, así que yo y cualquier otra persona que venga aquí sabe por qué esta es una solución pobre :)
Ben Leggiero

1
Simple y ordenado!
badhanganesh

9

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

Uso:

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

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

Esta respuesta es incorrecta. Si realiza dos instancias de la misma clase, devolverá la misma dirección de memoria para ambas instancias. Unmanaged.passUnretained(myObject).toOpaque()funciona correctamente en su lugar.
Padraig

@ Padraig Gracias, he actualizado con su código. Lamentablemente, ahora toma un AnyObjectparámetro. Preferiría Anycomo el tipo de entrada.
neoneye

6

En Swift4 sobre Array:

    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
Me salvó el día. Debería haber una insignia "la adquisición más reciente tan útil"
Anton Tropashko

De hecho, este es el único enfoque que imprimió mi self?.array.
Rostyslav Druzhchenko

4

Las otras respuestas están bien, aunque estaba buscando una manera de obtener la dirección del puntero como un entero:

let ptr = unsafeAddressOf(obj)
let nullPtr = UnsafePointer<Void>(bitPattern: 0)

/// This gets the address of pointer
let address = nullPtr.distanceTo(ptr) // This is Int

Solo un pequeño seguimiento.


Vea a continuación una versión de esta respuesta para Swift 3.
RenniePet

3

La respuesta que proporciona @Drew solo se puede usar para el tipo de clase.
La respuesta que proporciona @nschum solo puede ser para el tipo de estructura.

Sin embargo, si utiliza el segundo método para obtener la dirección de una matriz con un elemento de tipo de valor. Swift copiará toda la matriz porque en la matriz Swift se copia y escribe y Swift no puede asegurarse de que se comporte de esta manera una vez que pasa el control a C / C ++ (que se activa utilizando &para obtener la dirección). Y si usa el primer método, se convertirá automáticamente Arrayen NSArrayalgo que seguramente no queremos.

Entonces, la forma más simple y unificada que encontré es usando la instrucción lldb frame variable -L yourVariableName.

O puedes combinar sus respuestas:

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
}

3

Esto es para Swift 3.

Al igual que @CharlieMonroe, quería obtener la dirección como un número entero. Específicamente, quería la dirección de un objeto Thread para usar como ID de hilo en un módulo de registro de diagnóstico, para situaciones en las que no había un nombre de hilo disponible.

Basado en el código de Charlie Monroe, esto es lo que se me ocurrió hasta ahora. Pero cuidado, soy muy nuevo en Swift, esto puede no ser correcto ...

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

La última declaración está ahí porque sin ella estaba obteniendo direcciones como 0x60000007DB3F. La operación de módulo en la última instrucción convierte eso en 0x7DB3F.


3

Mi solución en Swift 3

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

este código crea una descripción como la descripción predeterminada <MyClass: 0x610000223340>


2

Ciertamente, esta no es la forma más rápida o segura de hacerlo. Pero funciona para mí. Esto permitirá que cualquier subclase nsobject adopte esta propiedad.

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

gracias por los comentarios Jeff, actualizaré la respuesta
Charlton Provatas

1
¡No importa! ¡Fue mi error! Su código funciona bien con una clase Swift pura. Lo siento por el error.
Jeff
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.