Швидке друкування змінної адреси пам'яті


165

Чи є все-таки змоделювання [NSString stringWithFormat:@"%p", myVar]від Objective-C новою мовою Swift?

Наприклад:

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

2
У [NSString stringWithFormat:@"%p", myVar], myVarповинен бути вказівник. У вашому коді Swift strне є вказівником. Тому порівняння не застосовується.
користувач102008

5
Варто зазначити, що, принаймні, коли я це набираю, вищевказані два коментарі є невірними.
Бен Леджієро

Відповіді:


112

Швидкий 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)")
}

2
unsafeAddressOf () працює лише для типів класів (як @Nick вказує нижче). Таким чином, ця відповідь працює лише в тому випадку, якщо Foundation імпортується, а String з'єднаний з NSString. У звичайному Swift String - це тип значення, і unsafeAddressOf не може бути використаний для отримання його адреси (спроба призводить до помилки компіляції).
Стівен Шауб

29
Що робити, якщо я хочу надрукувати адресу незмінного значення за допомогою Swift 3? withUnsafePointerпризводить до cannot pass immutable value as inout argumentпомилки.
Олександр Васенін

12
Незважаючи на те, що це прийнята і найвища відповідь, вона все одно дає неправильні результати. Приклад: print(self.description)друкує <MyViewController: 0x101c1d580>, ми використовуємо це як еталон. var mutableSelf = self; withUnsafePointer(to: &mutableSelf) { print(String(format: "%p", $0)) }друкує, 0x16fde4028що чітко відрізняється адресою. Хтось може пояснити, чому?
Олександр Васенін

4
До речі, це друкується 0x101c1d580як очікувалося:print(String(format: "%p", unsafeBitCast(self, to: Int.self)))
Олександр Васенін

8
@nyg Ваша відповідь дала мені зрозумілу причину фактичної помилки: UnsafePointerце структура, тому для того, щоб надрукувати адресу, на яку вказує (а не на саму структуру), ви повинні надрукувати String(format: "%p", $0.pointee)!
Олександр Васенін

213

Примітка. Це для довідкових типів.

Швидкий 4/5:

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

Друкує адресу пам'яті деякогоVar. (спасибі @Ying)


Швидкий 3.1:

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

Друкує адресу пам'яті деякогоVar.



2
Автокорекція Xcode 8.2.1 повідомляє мені, що це заразprint(Unmanaged<AnyObject>.passUnretained(someVar as AnyObject).toOpaque())
Джефф

2
Чи це неправда лише в тому випадку, якщо деякий вар є об'єктом. Якщо someVar буде типом значення, таким як структура, це дасть іншу адресу кожного разу, коли воно виконується.
OutOnAWeekend

3
@OutOnAWeekend Ви маєте рацію, швидше за все, тому що структура копіюється, коли передається як аргумент. Використовуючи Unmanagedце можна зробити так: print(Unmanaged<AnyObject>.fromOpaque(&myStruct).toOpaque()).
nyg

1
Станом на Swift 4, мені вдалося отримати і цей заклик до друку - Unmanaged.passUnretained(someVar).toOpaque()(не потрібна загальна специфікація)
Ян

2
Відповідь Swift 4 - ідеальна. Якщо ви також хотіли отримати представлення рядків без друку, рекомендую додати debugDescriptionйого в кінці.
Патрік

51

Зауважте, що ця відповідь була досить старою. Багато описуваних ним методів більше не працюють. Конкретно .coreбільше не можна отримати доступ.

Однак відповідь @ Draw правильна і проста:

Тепер це частина стандартної бібліотеки: unsafeAddressOf.

Тож відповідь на ваші запитання:

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

Ось оригінальна відповідь, яка була позначена правильною (для нащадків / ввічливості):

Свіфт "ховає" вказівники, але вони все ще існують під капотом. (оскільки час його виконання потрібен і з міркувань сумісності з 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 -> Переглянути пам'ять і перейдете до надрукованої адреси, ви побачите необроблені дані рядка. Оскільки це рядковий літерал, це адреса пам'яті всередині сховища двійкового файлу (не стека або купи).

Однак, якщо ви

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

    // example printed address 0x103f30020

Це буде в стеці, тому що рядок створюється під час виконання

ПРИМІТКА: .core._baseAdress не задокументований, я виявив, що він шукає інспектора змінної, і він може бути прихований у майбутньому

_baseAddress доступний не для всіх типів, ось ще один приклад із CInt

    var testNumber : CInt = 289
    takesInt(&testNumber)

Де takesIntтака функція помічника С, як ця

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

З боку Swift ця функція є takesInt(intptr: CMutablePointer<CInt>) , тому вона приймає CMutablePointer на CInt, і ви можете отримати її за допомогою & varname

Функція друкує 0x7fff5fbfed98, за цією адресою пам'яті ви знайдете 289 (у шістнадцятковій нотації). Ви можете змінити його вміст за допомогою*intptr = 123456

Тепер ще деякі речі, які потрібно знати.

Рядок, стрімко, - це примітивний тип, а не предмет.
CInt - тип Swift, відображений у тип C int.
Якщо ви хочете адресу пам'яті об'єкта, ви повинні зробити щось інше.
У Swift є кілька типів вказівників, які можна використовувати під час взаємодії з C, і про них ви можете прочитати тут: Типи вказівників Swift
Більше того, ви можете дізнатися більше про них, вивчаючи їх декларацію (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

так, вказівник повинен виходити, але не тільки з міркувань сумісності, як ви зверталися. покажчики, зазвичай використовувані операційною системою. навіть якщо мова є мовою високого рівня, покажчики повинні існувати будь-якою мовою в будь-який час в двигуні операційної системи.
holex

Я сказав "з причини сумісності І тому, що час його виконання потрібен" :-) друге твердження резюмує те, що ви говорите (я припускаю, що програміст це знає і розуміє, тому я витратив кілька слів)
LombaX,

Я припускаю, що це більше не застосовується?
aleclarson

Так. Я змінив правильну відповідь від @Drew.
Рог

@LombaX ми можемо друкувати лише адресу довідкового типу? Не можу я надрукувати адресу змінних?
АбхіманьяАрійський

21

Щоб отримати (купу) адресу об'єкта

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

Отже, за винятком струнних структур, всередині цього все значною мірою працює так само, як у (Об'єктивний) С.

(Поточний на Xcode 6.3)


Хм. Отримання адреси стека властивості об'єкта не є послідовним. Будь-які ідеї? Перевірте цю суть!
aleclarson

Властивості об'єкта знаходяться на купі, а не на стеці. Коли ви передаєте властивість екземпляра класу як UnsafePointer, Swift фактично спочатку копіює це значення, і ви отримуєте адресу копії. Я підозрюю, що це не дозволяє коду С обійти інтерфейс об'єкта і викликати непослідовний стан. Я не знаю, чи є такий спосіб.
nschum


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

( Суть )


У Swift ми маємо справу або з типовими типами (структури), або з типовими типами (класами). При виконанні:

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

Деяка пам’ять виділяється за адресою X, і за цією адресою ми знайдемо значення 42. Doing &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 (фу) та 0x5484 (бар).

В обох випадках використання &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).

Я сподіваюся, що це допомогло ... це зробило для мене 😆.


Щойно помітив, що при спробі надрукувати адресу однієї структури через 2 різні екземпляри MemoryLocationвидає 2 різних адреси.
користувач1046037

@ user1046037 Спасибі, внесли зміни до класу init. Я також отримую дві різні адреси, але лише тоді, коли використовую порожню структуру. Використання порожньої структури завжди дає мені дивні результати. Думаю, компілятор робить деякі оптимізації ...
ngg

@ user1046037 Перевірте: pastebin.com/mpd3ujw2 . Мабуть, усі порожні структури вказують на одну і ту ж адресу пам'яті. Однак, коли ми хочемо зберегти покажчик у змінній, він створить його копію (або структуру?) ...
nyg

Це цікаво, але коли надруковано двічі, він друкує різні адреси пам'яті. Те саме стосується і вищезазначених відповідей.
користувач1046037

@ user1046037 Я не впевнений, я розумію, що ти маєш на увазі, чи маєш ти код? (Я завжди отримую одну і ту ж адресу пам'яті)
nyg

15

Довідкові типи:

  • Має сенс отримати адресу пам'яті еталонного типу, оскільки вона представляє ідентичність.
  • === Оператор ідентичності використовується для перевірки, що 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

Типи значення:

  • Необхідність отримання адреси пам'яті типу значення не має великого значення (оскільки це значення), і акцент робиться більше на рівності значення.

12

Просто скористайтеся цим:

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

Якщо ви отримаєте скаргу компілятора на те, що " MyClass?" не відповідає CVarArg, ви можете це зробити extension Optional : CVarArg { }. В іншому випадку це, здається, друкує адреси без усієї "небезпечної" божевільності інших відповідей.
Девін Лейн

Це вимагає впровадження var _cVarArgEncoding: [Int]на CVarArg. Не ясно, як це потрібно здійснити.
Ючен Чжун

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

1
Будь-ласка, додайте до коментарів, де пояснюється чому, тому я і хтось, хто приходить сюди, знає, чому це погане рішення :)
Ben Leggiero

1
Просто і акуратно!
badhanganesh

9

Швидкий 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як тип введення.
neoneye

6

У 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
врятував мені день. Повинен бути знак "найкорисніший останній настільки придбання"
Антон Тропашко

Дійсно, це єдиний підхід, який надрукував мій self?.array.
Ростислав Дружченко

4

Інші відповіді чудові, хоча я шукав спосіб отримати адресу вказівника як ціле число:

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.
RenniePet

3

Відповідь @Drew надається лише для типу класу.
Відповідь @nschum надавати може лише для типу структура.

Однак якщо ви використовуєте другий метод для отримання адреси масиву з елементом типу value. Swift скопіює весь масив, оскільки масив Swift - це копія при записі, і Swift не може переконатися, що він веде себе таким чином, як тільки передає управління на C / C ++ (Що запускається, використовуючи &для отримання адреси). І якщо ви скористаєтесь першим методом замість цього, він автоматично перетвориться 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
}

3

Це для Swift 3.

Як @CharlieMonroe, я хотів отримати адресу як ціле число. Зокрема, я хотів, щоб адреса об'єкта Thread була використана як ідентифікатор потоку в модулі діагностичного журналу для ситуацій, коли не було назви імені потоку.

Виходячи з коду Чарлі Монро, ось що я придумав поки що. Але будьте обережні, я дуже новачок у Свіфта, це може бути не правильно ...

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


3

Моє рішення на Swift 3

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

цей код створює опис, як опис за замовчуванням <MyClass: 0x610000223340>


2

Це, звичайно, не найшвидший чи найбезпечніший спосіб досягти цього. Але це працює на мене. Це дозволить будь-якому підкласу 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

дякую за відгук Джефф, я
оновлю

1
Не звертай уваги! Це була моя помилка! Ваш код відмінно працює з чистим класом Swift. Вибачте за помилку.
Джефф
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.