В этой статье рассматриваются три эквивалентные техники выделения и освобождения динамической памяти в трех средах программирования: язык C, язык C++ и язык ассемблера ARM. Материалом для статьи послужил видеоролик [1] с замечательного YouTube-канала LaurieWired. Все примеры кода тестировались на Raspberry Pi [2] в операционной системе Raspberry Pi OS (Linux), процессор ARM.
[Выделение памяти на C+: new, delete]
Создайте файл allocate.cpp со следующими содержимым:
#include < iostream>
#include < new>
int main()
{
int *num_ptr = nullptr;
try
{
// Выделение блока памяти размером числа int:
num_ptr = new int;
// Использование выделенного блока, запись в него числа 4660 (0x1234):
*num_ptr = 0x1234;
// Вывод этого числа на печать:
std::cout << "Dynamic number: " << *num_ptr << std::endl;
}
catch (const std::bad_alloc& e)
{
return 1;
}
// Освобождение выделенной памяти. Память будет
// возвращена операционной системе:
delete num_ptr;
return 0;
}
Скомпилируйте файл allocate.cpp командой:
$ g++ allocate.cpp -o allocate
После компиляции получится исполняемый файл allocate, запустите его:
$ ./allocate
Dynamic number: 4660
На языке C++ для выделения памяти также можно использовать стандартные библиотечные вызовы malloc, free, realloc, которые используются при программировании на языке C.
[Выделение памяти на C: malloc, free]
Создайте файл allocate.c со следующими содержимым:
#include < stdio.h>
#include < stdlib.h>
int main ()
{
// Выделение блока памяти размером числа int:
int *num_ptr = (int*) malloc(sizeof(int));
// Проверка, была ли успешно выделена память:
if (NULL == num_ptr)
{
// Выделить память не получилось.
return 1;
}
// Использование выделенного блока, запись в него числа 4660 (0x1234):
*num_ptr = 0x1234;
// Вывод этого числа на печать:
printf("Dynamic number: %d\n", *num_ptr);
// Освобождение выделенной памяти:
free(num_ptr);
return 0;
}
Скомпилируйте allocate.c:
$ gcc allocate.c -o allocate
Запуск allocate:
$ ./allocate
Dynamic number: 4660
[Выделение памяти на языке ассемблера ARMv7: mmap, munmap]
На языке ассемблера мы будем использовать системные вызовы mmap, munmap [3] стандартной библиотеки C. Вызов mmap() создает новое отображение анонимного файла или устройства в виртуальное адресное пространство текущего вызывающего процесса. Декларация функций mmap, munmap на языке C (определения из sys/mman.h):
void *mmap(void addr[.length], size_t length, int prot, int flags,
int fd, off_t offset);
int munmap(void addr[.length], size_t length);
Начальный адрес для нового отображения задается параметром addr. Аргумент length задает размер выделяемой области в байтах (который должен быть больше 0).
Создайте файл allocate.s со следующим содержимым:
.section .text
extern printf
global main
// Подпрограмма, которая выделяет память: allocate:
push {lr}
// Подготовка параметров для вызова системной функции mmap:
mov r0, #0 /* addr: NULL, ядро OS само выберет адрес в памяти. */
mov r1, #4096 /* length: размер памяти для отображения (4096 байт, одна страница) */
mov r2, #3 /* prot: разрешения доступа к памяти, PROT_READ | PROT_WRITE */
mov r3, #0x22 /* flags: MAP_PRIVATE | MAP_ANONYMOUS */
mov r4, #-1 /* fd: -1, потому что мы не делаем отображение в файл */
mov r5, #0 /* offset: смещение 0 */
// Вызов системной функции mmap2. Номер системной функции 192
// для mmap2 (см. таблицу [4]) помещается в регистр r7:
mov r7, #192
svc #0
// Проверка результата вызова mmap. В случае успеха r0 будет содержать адрес
// выделенной памяти. Если же вызов был неудачным, то в регистре r0 будет
// значение -1:
cmp r0, #-1
beg alloc_failed
// Использование выделенной памяти, запись в неё числа 4660 (0x1234):
mov r1, #0x1234
str r1, [r0] /* Содержимое регистра r1 будет записано по адресу в регистре r0 */
b alloc_exit
alloc_failed:
mov r0, #-1
alloc_exit:
pop {pc}
main:
push {r4-r7, lr}
// Выделение памяти для числа:
bl allocate /* вызов подпрограммы allocate */
// Проверка: было ли выделение успешным?
cmp r0, #-1
beq print_fail
// Память была успешно выделена, и в неё было записано
// число 4660. Выведем на печать это число, как
// в предыдущих примерах.
push {r0}
ldr r1, [r0] /* Указатель на выделенную память в r0 */
ldr r0, =format_str
bl printf
// Освобождение памяти
pop {r0}
mov r1, #4096
// Системный вызов munmap:
mov r7, #215
svc #0
b exit
print_fail:
ldr r0, =format_str_fail
bl printf
exit:
ret
.section .data
format_str:
db "Dynamic number: %d\n", 0 format_str_fail:
db "Error call mmap\n", 0
Компиляция allocate.s:
$ arm-linux-gnueabi-as allocate.s -o allocate.o
$ arm-linux-gnueabi-gcc -static allocate.o -o allocate
Запуск allocate:
$ ./allocate
Dynamic number: 4660
[Ссылки]
1. Mastering Memory: Allocation Techniques in C, C++, and ARM Assembly site:youtube.com. 2. Raspberry Pi, быстрый старт. 3. mmap(2) Linux manual page site:man7.org. 4. Linux System Call Table site:googlesource.com. 5. Calling printf from the C standard library in assembly site:mourtada.se. |