0Pricing
Swift Academy · Урок

MemoryLayout и выравнивание

Исследуйте размер, шаг и выравнивание типов.

«MemoryLayout и выравнивание» — бесплатный урок Swift Academy на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Swift Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Swift Academy содержит 4 уроков всего.

Почему важна раскладка памяти

При работе с небезопасным кодом на Swift или при взаимодействии с C необходимо знать, как значения расположены в памяти. Swift предоставляет MemoryLayout<T>, позволяя изучать это, не обращаясь к raw bytes.

Он отвечает на три вопроса: каков размер значения, каково расстояние между значениями в массиве и на какой границе адреса должно начинаться значение.

// MemoryLayout works on any type
print(MemoryLayout<Int>.size)       // 8 on 64-bit
print(MemoryLayout<Bool>.size)      // 1
print(MemoryLayout<Double>.size)    // 8

size — Фактическое хранилище

MemoryLayout<T>.size — это количество bytes, занимаемых значимыми данными значения. Сюда не входит завершающее выравнивание, которое может располагаться между элементами массива.

Используйте size, когда хотите узнать, сколько значимых bytes содержит одно значение.

struct Point {
    var x: Int8   // 1 byte
    var y: Int64  // 8 bytes
}

print(MemoryLayout<Point>.size)   // 16 (not 9 — padding!)

stride — Расстояние в массивах

stride — это расстояние в bytes между началами соседних элементов при размещении значений в непрерывном массиве. Оно равно size, округлённому вверх до значения alignment.

При ручном выделении буферов умножайте количество элементов на stride, а не на size.

print(MemoryLayout<Point>.stride) // 16
print(MemoryLayout<Int8>.stride)  // 1

// Buffer for 10 Points needs:
let bytes = 10 * MemoryLayout<Point>.stride // 160

alignment — Границы адресов

alignment — это граница bytes, на которой должно начинаться значение. Значение Int64 с alignment 8 должно начинаться по адресу, кратному 8. Компилятор добавляет выравнивание, чтобы выполнить это требование.

alignment структуры равен наибольшему alignment её членов.

print(MemoryLayout<Int8>.alignment)   // 1
print(MemoryLayout<Int64>.alignment)  // 8
print(MemoryLayout<Point>.alignment)  // 8

Разница между size и stride

Для многих типов size == stride, но при наличии выравнивания они различаются. Размер структуры может быть равен size 9, а её stride — 16, поскольку следующий элемент должен быть выровнен по границе 8 bytes.

Практическое правило: рассуждайте об одном значении с помощью size, а о коллекциях — с помощью stride.

struct Mixed {
    var flag: Bool   // 1
    var value: Int   // 8
}
print(MemoryLayout<Mixed>.size)   // 16
print(MemoryLayout<Mixed>.stride) // 16

Смещения полей

Для структур можно узнать положение каждого сохранённого свойства относительно начала с помощью MemoryLayout<T>.offset(of:) и пути к свойству. Это необходимо, когда данные должны иметь раскладку, соответствующую структуре C.

struct Header {
    var magic: UInt32  // offset 0
    var length: UInt64 // offset 8 (padded)
}

print(MemoryLayout<Header>.offset(of: \Header.magic) ?? -1)  // 0
print(MemoryLayout<Header>.offset(of: \Header.length) ?? -1) // 8

Изменение порядка для экономии места

Из-за выравнивания порядок сохранённых свойств влияет на размер. Если разместить сначала более крупные поля или сгруппировать поля по размеру, структуру можно уменьшить. Это особенно важно для больших массивов, где лишние bytes накапливаются.

struct Wasteful { var a: Int8; var b: Int64; var c: Int8 } // size 24
struct Tight    { var b: Int64; var a: Int8; var c: Int8 } // size 16

print(MemoryLayout<Wasteful>.size)
print(MemoryLayout<Tight>.size)

Раскладка ссылок

Экземпляр класса размещается в куче, поэтому переменная, содержащая ссылку на класс, представляет собой всего лишь указатель. Её MemoryLayout.size равен размеру указателя (8 bytes), независимо от размера объекта.

Типы-значения, например структуры, хранят содержимое непосредственно; ссылочные типы хранят только указатель.

class Big { var data = [Int](repeating: 0, count: 1000) }

print(MemoryLayout<Big>.size) // 8 — just the reference

Необязательные значения и перечисления

Необязательные значения обычно добавляют один byte для тега, но Swift оптимизирует ссылочные необязательные значения, повторно используя нулевой указатель, поэтому String? может иметь тот же размер, что и String.

Размер перечислений со связанными значениями достаточен для хранения самого крупного варианта и дискриминатора.

print(MemoryLayout<Int?>.size)    // 9
print(MemoryLayout<String>.size)  // 16
print(MemoryLayout<String?>.size) // 16 — no extra byte

Использование of: с экземпляром

У каждого свойства (size, stride, alignment) есть также форма для экземпляра: MemoryLayout.size(ofValue:). Это удобно, когда тип сложно явно записать или он выводится автоматически.

let tuple = (UInt8(1), UInt64(2))
print(MemoryLayout.size(ofValue: tuple))      // 16
print(MemoryLayout.stride(ofValue: tuple))    // 16
print(MemoryLayout.alignment(ofValue: tuple)) // 8

Выделение с правильной раскладкой

При выделении raw-памяти необходимо передать и количество bytes, и alignment. Получение этих значений из MemoryLayout обеспечивает корректность на разных архитектурах, вместо жёсткого задания чисел.

let count = 4
let capacity = count * MemoryLayout<Int>.stride
let align = MemoryLayout<Int>.alignment

let raw = UnsafeMutableRawPointer.allocate(
    byteCount: capacity, alignment: align)
defer { raw.deallocate() }

Быстрая проверка

Проверьте, как вы понимаете разницу между size и stride.

Повторение

Вы изучили три основы раскладки памяти:

  • size — значимые bytes одного значения
  • stride — расстояние между элементами массива; используйте его при выделении памяти
  • alignment — требуемая граница начала

Вы также увидели offset(of:) для определения положения полей, узнали, как порядок свойств влияет на размер, и выяснили, что ссылки имеют размер указателя. Эти средства позволяют безопасно выделять и интерпретировать raw-память.

Часто задаваемые вопросы

Урок «MemoryLayout и выравнивание» бесплатный?

Да — полный текст урока «MemoryLayout и выравнивание» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Swift Academy, подпишись на CoddyKit PRO. Курс Swift Academy содержит 4 уроков всего.

Чему я научусь в уроке «MemoryLayout и выравнивание»?

Исследуйте размер, шаг и выравнивание типов. Ты практикуешь Swift Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Swift Academy?

Предыдущий опыт не требуется. Swift Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.

Сколько времени занимает урок «MemoryLayout и выравнивание»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Swift Academy?

Да. Каждый урок Swift Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. MemoryLayout и выравнивание
  2. UnsafePointer и UnsafeMutablePointer
  3. Указатели на небезопасные буферы
  4. withUnsafeBytes и взаимодействие с C
← Назад к Swift Academy