Справочник от Автор24
Поделись лекцией за скидку на Автор24

Низкоуровневые средства C++ для работы с памятью

  • ⌛ 2017 год
  • 👀 261 просмотр
  • 📌 214 загрузок
  • 🏢️ МЭИ
Выбери формат для чтения
Загружаем конспект в формате pdf
Это займет всего пару минут! А пока ты можешь прочитать работу в формате Word 👇
Конспект лекции по дисциплине «Низкоуровневые средства C++ для работы с памятью» pdf
Низкоуровневые средства C++ для работы с памятью Курс «Разработка ПО систем управления» Кафедра управления и информатики НИУ «МЭИ» Осень 2017 г. Динамическое выделение памяти • Выделение блока памяти под 10 целых. int* xs = new int[10]; • Обращение к элементам блока (массива): *xs == xs[0] *(xs + 5) == xs[5] • Освобождение блока: • delete[ ] xs; • Выделенное new освобождают delete, new[ ] — delete[ ]. • sizeof(xs) == sizeof(int*) == 8 // или 4 • xs[42] // undefined behavior, но компилируется Осень 2017 г. © Кафедра УиИ НИУ «МЭИ» 2 Адресная арифметика • int xs[10]; // sizeof(int) == 4 • xs == & xs == & xs + 0 == & xs[0] == 0 + &xs = 0[xs] • & xs[10] – & xs[5] == (xs + 10) – (xs + 5) == 5 ➢ Вычитание указателей на Type дает количество элементов типа Type между ними. • Не количество байт! • xs + 1 == & xs[0] + 1 == (& xs + 0) + 1 == & xs[1] ➢ Сложение указателя на Type и числа дает указатель, смещенный на размер Type (на один Type). • Не на один байт! Осень 2017 г. © Кафедра УиИ НИУ «МЭИ» 3 N-мерные дин. массивы • size_t width = …; size_t height = …; double* matrix = new double[width * height]; • std::vector matrix(width * height); • matrix[ i, j ], matrix[ i ] [ j ] — неправильно! • matrix[ i * width + j ] height matrix width • Можно расположить элементы в памяти иначе. Эффективнее обращаться к памяти последовательно. width • Например, если обработка идет по столбцам, стоит группировать элементы по ним (column-major). Осень 2017 г. © Кафедра УиИ НИУ «МЭИ» 4 Встроенные массивы • double data [ 42 ]; double table [ 7 ] [ 6 ]; • Размер задается при компиляции и не меняется. Индексация с нуля: • data [ 0 ] • table [ 0 ] [ 0 ] // table [0, 0] — неправильно! размер всего массива • количество элементов = размер одного элемента: size_t const size = sizeof ( data ) / sizeof ( data [ 0 ] ); • Преобразуются к указателям: double* start_item_pointer = data; • Не копируются: double mean = get_mean ( data, size ); // double get_mean ( double* data, size_t size ); • Массив в составе структуры копируется вместе со структурой. Осень 2017 г. © Кафедра УиИ НИУ «МЭИ» 5 Класс-массив std::array • Удобная «обертка» (wrapper) для встроенных массивов: • создание объекта не занимает времени • (создание вектора требует выделения памяти); • поддерживает копирование; • можно передавать по ссылке, когда не нужно; • можно получить указатель как для массива методом data(); • поддерживается присваивание; • позволяет получить размер методом size(); • итераторы, проверка индексов, поэлементное сравнение. • Резюме: • «вектор фиксированного размера»; • замена простым массивам почти всюду. • array < double, 42 > data { 1, 2, 3 }; cout << data [ data . size() / 2 ]; Осень 2017 г. © Кафедра УиИ НИУ «МЭИ» 6 Проблемы использования динамической памяти • Использование адресов: использовано освобождается если переместить объект, нужно менять все указатели на него. • Время выделения памяти: • крайне непредсказуемо; • зависит от состояния памяти (нужно найти подходящую область). новый потери • Фрагментация памяти: см. рис. Уровень потерь от фрагментации может стать сопоставим с объемом памяти: Осень 2017 г. © (спустя время) По мотивам слайдов Бьярне Страуструпа. 7 Размер типов данных (1) • Оператор sizeof определяет размер в байтах: int value; sizeof ( value ) == sizeof ( int ) == 4 // байта • Работает во время компиляции: • размер объекта-вектора (указатель на данные и число-длина): vector < int > data(10); sizeof ( data ) == 8 // возможно • способ определить размер данных в векторе: data . size() * sizeof ( int ) Осень 2017 г. © Кафедра УиИ НИУ «МЭИ» 8 Размер типов данных (2) • Бывает нужно задавать размер точно, обычно когда формат данных задан наперед. • Есть специальные типы данных (): • uint8_t, uint16_t, uint32_t, uint64_t • Размер зависит от компилятора и платформы: • sizeof(long int) == 4 // 32 бита (вероятно!) • sizeof(long int) == 8 // 64 бита • Полагаться на размер чревато ошибками: • 0xFFFFFFFF == 0b ' 11111111 ' 11111111 ' 11111111 ' 11111111 • unsigned long int maximum = 0xFFFFFFFF; • Максимальное возможное значение при 32 битах. • При 64 битах — нет (максимальное в 4 млрд. раз больше). • , std::numeric_limits Осень 2017 г. © Кафедра УиИ НИУ «МЭИ» 9 Выравнивание (alignment) • Явление: sizeof ( uint8_t ) == 1 sizeof ( int16_t ) == 2 sizeof ( Device ) == 8 1 • Компилятор располагает данные по адресам, кратным 4 (например); часть памяти не используется. 4 control 1 8 result 2 6 • Иногда это работает быстрее (x86). • Иногда это необходимо (ARM). 8 • Иногда это недопустимо! • Когда расположение данных (layout) диктуется извне (как для Device). • В любой компилятор встроены способы отказаться от выравнивания. Осень 2017 г. © Кафедра УиИ НИУ «МЭИ» # pragma pack ( push, 1 ) struct Device { … }; # pragma pack ( pop ) 10 Порядок байт (endianness) в представлении целых типов • 123410 = 04D216 , 𝐹𝐹16 < 04𝐷216 < 𝐹𝐹𝐹𝐹16 ⇒ 2 байта • Мы пишем от старших разрядов (0416 ) к младшим (𝐷216 ). • Какой байт в памяти расположен первым? Есть варианты: • от младших к старшим (little-endian, LE, Intel): 𝐷2 04, • от старших к младшим (big-endian, BE, «сетевой»): 04 0𝐷, • смешанный (экзотика): 0x12345678  34 12 78 56. • Встречается: • процессоры Intel и AMD (ПК, обычные серверы): little-endian. • процессоры ARM (мобильные устройства): могут переключать во время работы, обычно big-endian. • серверы IBM, крупные серверы HP: big-endian (обычно). • При работе с двоичными данными нужно знать endianness. • число в LE + число в BE = бессмысленное значение Осень 2017 г. © Кафедра УиИ НИУ «МЭИ» 11 Оператор reinterpret_cast • Устройство представляется в памяти как набор переменных: struct Device { uint8_t control; int16_t result; }; Измерение начинается при записи в этот байт. control Результат измерения появляется здесь. result • Известно, что такая структура находится по адресу 0x0300. Device* device = reinterpret_cast < Device* > ( 0x0300 ); device -> control = 1; double voltage = device -> result / 32768.0 * 5; // −5…+5 В • Курс «Технические средства автоматизации и управления» весной. Осень 2017 г. © Кафедра УиИ НИУ «МЭИ» 12 Побитовые операции & | ^ << >> ~ © И a 1 0 1 0 1 0 1 0 0xAA ИЛИ b 0 0 0 0 1 1 1 1 0x0F a&b 0 0 0 0 1 0 1 0 0x0A исключающее ИЛИ сдвиг влево сдвиг вправо НЕ По мотивам слайдов Бьярне Страуструпа. Осень 2017 г. a | b 1 0 1 0 1 1 1 1 0xAF a^b 1 0 1 0 0 1 0 1 0xA5 a << 1 0 1 0 1 0 1 0 0 0x54 b >> 2 0 0 0 0 0 0 1 1 0x03 ~b 1 1 1 1 0 0 0 0 0xF0 © Кафедра УиИ НИУ «МЭИ» 13 Битовые флаги Если установлен этот бит, файл можно читать. • uint8_t constexpr CAN_READ = 04; // 0b ' 1 0 0 uint8_t constexpr CAN_WRITE = 02; // 0b ' 0 1 0 uint8_t constexpr CAN_EXECUTE = 01; // 0b ' 0 0 1 Разные биты! • Задание набора флагов логическим «ИЛИ»: uint8_t CAN_EVERYTHING = CAN_READ | CAN_WRITE | CAN_EXECUTE; // == 04 | 02 | 01 == 0b'100 | 0b'010 | 0b'001 == 0b'111 == 07 • Проверка наличия флага логическим «И»: uint8_t permissions = 05; if (permissions & CAN_READ) { … } // 05 & 04 == 0b'101 & 0b'100 == 0b'100 != 0 → true Осень 2017 г. © Кафедра УиИ НИУ «МЭИ» 14 Битовые маски и сдвиги ▪ Задача: получить биты 4…15 из uint32_t. ✓Решение: • • • • сдвинуть нужные биты к началу числа (в 0…11); full << 4 оставить только нужные биты (остальные обнулить). (full << 4) & 0b'1111'1111'1111'0000 // 0xFFF0 ▪ Задача: установить 7-й бит в value. ✓Решение: value = value | (1 >> 7); • std :: vector < bool > • std :: bitset < 314 > Осень 2017 г. © Кафедра УиИ НИУ «МЭИ» 15 Числа с плавающей запятой (floating-point numbers) • Представлены в памяти нетривиально. • IEEE 754: 𝑥 = 𝑀 ∙ 2𝐸 , 𝑀 и 𝐸 целые, и есть исключения. • Некоторые «простые» десятичные дроби (0,1) нельзя представить точной двоичной дробью. • Имеют конечную точность. • Математически равные результаты, вычисленные по-разному, могут не быть точно (побитово) равны. • При операциях над числами разного порядка возможна потеря точности: • 1000000.0f + 0.01f == 1000000.0f // Копейка рубль бережет, а миллион копейку — нет :-) • Практика: не годятся для представления денежных сумм. Осень 2017 г. © Кафедра УиИ НИУ «МЭИ» 16 Сравнение чисел с плавающей запятой • Проверка на равенство: float x = 0.3333333f; float y = 1.0f / 3.0f; if (x == y) // false из-за ошибки округления if (abs(x – y) < N * EPS) // Корректно; но что такое N и EPS? • EPS — «машинное эпсилон», 1.0f + EPS == 1.0f из-за конечной точности. • FLT_EPSILON, DBL_EPSILON в . • См. курс вычислительной математики (ВМ-2). • N зависит от способа вычисления x и y, но на практике выбирают, например, N = 16. Осень 2017 г. © Кафедра УиИ НИУ «МЭИ» 17 Числа с фиксированной запятой (fixed-point numbers) • Дробные величины представляют целыми числами (пример: не 1 р. 50 к., а 150 к.). • Нет потерь точности (у всех чисел она равна). • Высокая производительность. • Ограничен диапазон (в т. ч. снизу). • Пример: • using Money = uint16_t; // Деньги в копейках. • Money price = 20050; // 200 р. 50 к. • Диапазон: {0, 1 к., …, 655 р. 35 к.} Осень 2017 г. © Кафедра УиИ НИУ «МЭИ» 18 Строки C (C-style strings) Строка C — массив символов, завершающийся нулевым символом '\0'. char greeting [ ] = "Hello!"; • Размер определится автоматически (работает для любых встроенных массивов). • Длина строки — 6 символов. • sizeof ( greeting ) == 7 • // char greeting [ 7 ] { 'H', 'e', 'l', 'l', 'o', '!', '\0' }; const char* farewell = "Goodbye!"; • sizeof ( farewell ) == 4 // размер указателя • Длина строки — 8 символов, где-то в памяти их 9. Осень 2017 г. © Кафедра УиИ НИУ «МЭИ» 19 Обработка строк C size_t get_string_length ( const char* symbols ) { Разыменование дает символ, size_t length = 0; на который указывает symbols. while ( * symbols ) { Если это '\0', условие ложно. + + length; Смещение указателя + + symbols; к адресу очередного символа. } return length; } × Если symbols == nullptr, нельзя делать *symbols. × 𝒪 𝑙𝑒𝑛𝑔𝑡ℎ Осень 2017 г. © Кафедра УиИ НИУ «МЭИ» 20 Копирование строк C void copy_string (char* to, const char* from) { while ( * from ) { 1) Пока есть символ для копирования, * to = * from; 2) копировать его 3) и перейти к следующей ячейке для копии, + + to; 4) а также к следующему исходному символу. + + from; } 5) Скопировать нулевой символ. * to = * from; // while ( * to ++ = * from ++); } Предполагается, что массив, на который указывает to, достаточно велик, чтобы вместить символы из from. Проверить это в copy_string() нельзя. Осень 2017 г. © Кафедра УиИ НИУ «МЭИ» 21 Работа со строками Класс std::string Cтроки C () 48 string name, message; const string greeting = "Hello"; char name [ 32 ], message [ 32 ]; const char* greeting = "Hello"; getline ( cin, name ); fgets ( name, sizeof(name), stdin ); // gets() небезопасна! message = greeting; strcpy ( message, greeting ); message += ", " + name + "!"; strcat ( message, ", " ); strcat ( message, name ); strcat ( message, "!" ); cout << message << '\n'; puts ( message ); Осень 2017 г. © Кафедра УиИ НИУ «МЭИ» // cout << … 22 Литература к лекции ▪ Programming Principles and Practices Using C++: • глава 25 — тема лекции; • раздел 27.5 — строки C; • аналогичная презентация (скорее, наоборот :-). ▪ C++ Primer: • разделы 3.5 и 3.6 — подробно о массивах. ▪ Сайт «C++ Reference»: • функции для работы с памятью и строками; • ограничения типов с плавающей запятой; • описание std :: array, std :: vector < bool >, std :: bitset. ▪ Статья о плавающей запятой. Осень 2017 г. © Кафедра УиИ НИУ «МЭИ» 23
«Низкоуровневые средства C++ для работы с памятью» 👇
Готовые курсовые работы и рефераты
Купить от 250 ₽
Решение задач от ИИ за 2 минуты
Решить задачу
Помощь с рефератом от нейросети
Написать ИИ

Тебе могут подойти лекции

Смотреть все 588 лекций
Все самое важное и интересное в Telegram

Все сервисы Справочника в твоем телефоне! Просто напиши Боту, что ты ищешь и он быстро найдет нужную статью, лекцию или пособие для тебя!

Перейти в Telegram Bot