УДК 004.424

О ДИНАМИЧЕСКОМ ВЫДЕЛЕНИИ ПАМЯТИ ДЛЯ ЭЛЕМЕНТОВ МАССИВА В С++

Дмитриев Владислав Леонидович1, Батыршин Артем Ильдарович1
1Стерлитамакский филиал Башкирского государственного университета

Аннотация
В статье рассматриваются основы работы с динамической памятью на языке программирования С++ при размещении элементов массива. Приведены практические примеры, демонстрирующие работу со стандартной операцией new и операцией new с адресацией. Дается анализ полученных результатов.

Ключевые слова: адрес ячейки памяти, выделение памяти, динамическая память, программирование


ABOUT DYNAMIC MEMORY ALLOCATION FOR ARRAY ELEMENTS IN C++

Dmitriev Vladislav Leonidovich1, Batirshin Artem Ildarovich1
1Sterlitamak branch of the Bashkir state University

Abstract
The article discusses the basics of working with dynamic memory in the C++ programming language when placing the array elements. Presented the practical examples of working with the standard operator new and operator new with addressing. Given the analysis of the obtained results.

Keywords: dynamic memory, memory allocation, programming, the address of the memory cell


Рубрика: Педагогика

Библиографическая ссылка на статью:
Дмитриев В.Л., Батыршин А.И. О динамическом выделении памяти для элементов массива в С++ // Психология, социология и педагогика. 2017. № 1 [Электронный ресурс]. URL: https://psychology.snauka.ru/2017/01/7502 (дата обращения: 28.10.2023).

Язык программирования C++ широко используется для разработки программного обеспечения, и является одним из самых популярных языков программирования. Область его применения включает создание операционных систем, разработку прикладных программ, написание драйверов устройств, а также разнообразных развлекательных приложений. В настоящее время существует множество реализаций языка C++, как коммерческих, так и бесплатных (например, Microsoft Visual C++ 2005-2013).

Особое внимание при обучении программированию на С++ следует уделять работе с динамической памятью [2], так как именно при работе с ней учащиеся допускают большое количество ошибок.

В данной статье рассматриваются вопросы выделения динамической памяти при работе с массивами. Предполагается, что учащиеся уже имеют базовые представления о языке программирования C++.

Пределы динамического выделения памяти ограничены только объемом доступной физической памяти (или доступной виртуальной памяти). Динамическое выделение памяти позволяет в процессе выполнения программы увеличивать область памяти для хранения новых элементов и освобождать ресурсы памяти, в которых уже нет необходимости.

Динамическое распределение памяти регулируется такими основными операциями, как new и delete. Первая операция принимает в качестве аргумента тип динамически размещаемого объекта и возвращает указатель на объект этого типа. Вторая операция освобождает область памяти, выделенную операцией new (эта область памяти возвращается системе), и она может быть снова в дальнейшем выделяться при необходимости.

Для демонстрации работы с динамической памятью приведем пример программы, которая запрашивает элементы одномерного массива и затем выводит их. Динамическую память будем использовать для хранения элементов массива, при этом количество элементов массива будем указывать в процессе выполнения программы (это позволит резервировать необходимый в данный момент объем памяти).

#include <iostream>

#include <conio.h>

using namespace std;

int main()

{ setlocale(0,”rus”);

int i,n;

int *p= new int [n]; // выделяем память

cout<<”Введите размер массива: “;

cin>>n;

cout<<”n”;

for (i=0; i<n; i++) cin>>p[i];

cout<<”Вы ввели массив: “;

for (i=0; i<n; i++) cout<<p[i]<<” “;

delete[ ] p; // освобождаем память

getch(); return 0;}

Перепишем теперь эту же программу несколько иначе:

#include <iostream>

#include <conio.h>

using namespace std;

int main()

{ setlocale(0,”rus”);

int i,n;

int *p= new int [n];

cout<<”Укажите количество элементов массива: “;

cin>>n;

cout<<”n”;

for (i=0; i<n; i++) cin>>*p++;

cout<<”Вы ввели массив: “;

for (i=0; i<n; i++) cout<<*–p<<” “;

delete[ ] p;

getch(); return 0;}

Однако стоит отметить, что последняя реализация менее безопасна, чем первая, т.к. в этом случае для перехода к следующему элементу массива используется операция инкремента, которая на последней итерации цикла for смещает указатель за пределы массива. Именно поэтому в цикле вывода элементов применяется префиксная операция декремента (чтобы вернуть указатель в пределы массива).

При выделении памяти под двумерный массив необходимо сначала выделить память под массив указателей на одномерные массивы (т.к. можно считать, что любой двумерный массив состоит из набора строк – одномерных массивов), и только потом выделить память для одномерных массивов, например:

int n;

int **MI;

MI = new int *[n]; //память под массив указателей на строки

. . . . . . . . . . . . . . .;

for (i=0; i<n; i++)

{MI[i]=new int[m]; //память под элементы одномерных массивов

for (j=0; j<m; j++) cin>>MI[i][j];}

Память для массива более высокой размерности выделяется аналогичным образом. Необходимо, однако, помнить, что ненужную для дальнейшего выполнения программы память следует освобождать.

Выше была рассмотрена операция new, отвечающая за поиск блока памяти, обладающего достаточным размером для удовлетворения запроса памяти. Существует еще одна разновидность операции newоперация new с адресацией. Она позволяет указать адрес используемого блока памяти, и может быть использована, например, при работе с каким-либо оборудованием, доступ к которому осуществляется по строго определенным адресам.

Перед началом работы с операцией new с адресацией может потребоваться подключить заголовочный файл new, содержащий прототип этой версии операции. Синтаксис работы операции new с адресацией остается таким же, как и в случае обычной операции new, с той лишь разницей, что операция new с адресацией использует аргумент, указывающий требуемый адрес.

Рассмотрим пример программы с динамическими массивами, поясняющий отличия в работе с двумя разновидностями операции new.

#include “stdafx.h”

#include <iostream>

#include <iomanip>

using namespace std;

const int N=5;

char buf[1024];

int main()

{ setlocale(LC_ALL,”rus”);

//———————————————————————————

double *p1, *p2;

p1 = new double [N];

p2 = new (buf) double [N];

for(int i=0; i<N; p1[i]=p2[i]=5.2*i++);

cout<<”Первый вызов операций:n”;

cout<<”Адресация памяти:ntnew: “<<p1

     <<”ntnew с адресацией: “<<(void*)buf

     <<”nnСодержимое ячеек памяти:n”;

for(int i=0; i<N; i++)

{cout<<”Адрес: “<<&p1[i]<<”, содержимое: ” <<setw(5)<<p1[i]<<”; “;

cout<<”Адрес: “<<&p2[i]<<”, содержимое: ” <<setw(5)<<p2[i]<<endl;}

//———————————————————————————

double *p3, *p4;

p3 = new double [N];

p4 = new (buf) double [N];

for(int i=0; i<N; p3[i]=p4[i]=5.2*i++);

cout<<”nnВторой вызов операций:n”;

cout<<”Адресация памяти:ntnew: “<<p3

     <<”ntnew с адресацией: “<<(void*)buf

     <<”nnСодержимое ячеек памяти:n”;

for(int i=0; i<N; i++)

{cout<<”Адрес: “<<&p3[i]<<”, содержимое: ” <<setw(5)<<p3[i]<<”; “;

cout<<”Адрес: “<<&p4[i]<<”, содержимое: ” <<setw(5)<<p4[i]<<endl;}

//———————————————————————————

double *p5, *p6;

p5 = new double [N];

p6 = new (buf+N*sizeof(double)) double [N];

for(int i=0; i<N; p5[i]=p6[i]=5.2*i++);

cout<<”nnТретий вызов операций:n”;

cout<<”Адресация памяти:ntnew: “<<p5

     <<”ntnew с адресацией: “<<(void*)buf

     <<”nnСодержимое ячеек памяти:n”;

for(int i=0; i<N; i++)

{cout<<”Адрес: “<<&p5[i]<<”, содержимое: ” <<setw(5)<<p5[i]<<”; “;

cout<<”Адрес: “<<&p6[i]<<”, содержимое: ” <<setw(5)<<p6[i]<<endl;}

delete []p1;

delete []p3;

delete []p5;

system(“pause”); return 0;}

Ниже на рис. 1 показан результат работы представленной программы. Кратко проанализируем полученные результаты.

Во-первых, обратим внимание на то, что операция new с адресацией действительно помещает массив p2 в массив buf. Для переменных buf и p2 установлено значение адреса 00419188. Обычная адресация new выделяет массиву p1 адрес памяти со значением 003651F8.

Во-вторых, при втором вызове операций new, обычная операция new выбирает другой блок памяти, начинающийся с адреса 00366D50. Однако операция new с адресацией использует всё тот же блок памяти, что и прежде, и начинающийся с адреса 00419188. Это связано с тем, что операция new с адресацией просто использует передаваемый в качестве аргумента адрес памяти; она не анализирует, свободна ли указанная область памяти, а также не выполняет поиск блока еще неиспользованной памяти. В связи с этим при третьем вызове операций new для операции new с адресацией предусмотрено смещение в массиве buf, чтобы использовать еще неиспользованную область памяти (смещение на 40 байт).


Рисунок 1. Результат работы программы, демонстрирующей работу с операциями new и new с адресацией

В-третьих, для операции new с адресацией не используется операция delete, так как использовать ее, по сути, невозможно. Дело в том, что для переменной buf выделяется область в статической памяти, а операция delete может использоваться только для указателя на область динамической памяти (памяти кучи), выделенной обычной операцией new. Если всё же попробовать использовать операцию delete для переменных p2, p4 или p6, то это вызовет ошибку времени выполнения. Однако если бы для создания буфера памяти buf использовалась обычная операция new, то для освобождения такого блока памяти необходимо было бы применить операцию delete.

После изучения представленного выше материала с целью оценки уровня усвоения учащимися учебного материала может оказаться удобным использование всевозможных программ-тестов [3, 4], в том числе в дистанционной форме.

В заключение хочется особо отметить, что для того, чтобы научиться программировать, не достаточно прочитать книгу, посвященную тому или иному языку программирования, необходимо уделять достаточно времени для практического написания программ, постепенно усложняя поставленные перед собой задачи. Кроме того, необходимо постоянно находиться в творческом поиске, так как путей решения поставленной задачи может быть несколько, и практически всегда первоначальное решение может быть оптимизировано [1, 5-9].


Библиографический список
  1. Дмитриев В.Л., Ахмадеева Р.З. Развитие конструктивного мышления при изучении программирования // Информатика и образование. 2009. № 2. – С. 69-73.
  2. Дмитриев В.Л. Теория и практика решения задач по программированию. Ч.1. Уфа: РИЦ БашГУ. 2007. – 264 с.
  3. Дмитриев В.Л. Тестирование в игровой форме как способ проверки усвоения учебного материала // Информатика в школе. 2012. №10 (83). – С. 41-43.
  4. Дмитриев В.Л. Компьютерная программа для проведения тестирования с поддержкой произвольного расположения материалов теста // Информатика и образование. 2014. №2 (251). – С. 74-77.
  5. Дмитриев В.Л. Поэтапная разработка программы в среде Turbo Pascal на примере поиска пути с использованием волнового алгоритма // Информатика и образование. 2013. № 8. – С. 29-33
  6. Дмитриев В.Л. Об эффективных алгоритмах решения ряда задач при обучении программированию // Профильная школа. 2014. Т.2. №3. – С. 19-26.
  7. Дмитриев В.Л. Об эффективных решениях в задачах по программированию // Информатика в школе. 2015. № 9 (112). – С.  37-40.
  8. Дмитриев В.Л. Эффективные алгоритмы в задачах по программированию // Профильная школа. 2016. Т.4. №2. С. 53-61.
  9. Окулов С.М. Программирование в алгоритмах. – М.: БИНОМ. Лаборатория знаний. 2002. – 341 с.


Все статьи автора «Дмитриев Владислав Леонидович»


© Если вы обнаружили нарушение авторских или смежных прав, пожалуйста, незамедлительно сообщите нам об этом по электронной почте или через форму обратной связи.

Связь с автором (комментарии/рецензии к статье)

Оставить комментарий

Вы должны авторизоваться, чтобы оставить комментарий.

Если Вы еще не зарегистрированы на сайте, то Вам необходимо зарегистрироваться: