Процедуры работы с динамической памятью


Содержание

Работа с динамической памятью

Динамическая память является ресурсом операционной системой и выделяется по явному запросу процесса.

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

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

Ошибка сегментации (Segmentation fault)

Если процесс попытается использовать «чужую» память (что в защищенном режиме работы процессора в принципе невозможно из-за механизма виртуальной адресации), обратившись по некоторому случайному адресу, операционная система аварийно завершит процесс с выводом предупреждения пользователю.

Пример

void foo(int *pointer)
<
*pointer = 0; //потенциальный Segmentation fault
>

int main()
<
int *p;
int x;
*NULL = 10; //совсем очевидный Segmentation fault
*p = 10; //достаточно очевидный Segmentation fault
foo(NULL); //скрытый Segmentation fault
scanf(«%d», x); //скрытый и очень популярный у новичков на Си Segmentation fault

Утечка памяти (Memory leak)

Если процесс попросил у ОС память, а затем про нее забыл и более не использует, это называется утечкой памяти.

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

Пример

void swap_arrays(int *A, int *B, size_t N)
<
int * tmp = (int *) malloc(sizeof(int)*N); //временный массив
for(size_t i = 0; i < N; i++)
tmp [i] = A[i];
for(size_t i = 0; i < N; i++)
A[i] = B[i];
for(size_t i = 0; i < N; i++)
B[i] = tmp [i];
//выходя из функции, забыли освободить память временного массива
>

int main()
<
int A[10] = <1, 2, 3, 4, 5, 6, 7, 8, 9, 10>;
int B[10] = <10, 9, 8, 7, 6, 5, 4, 3, 2, 1>;
swap_arrays(A, B, 10); //функция swap_arrays() имеет утечку памяти

int *p;
for(int i = 0; i < 10; i++) <
p = (int *)malloc(sizeof(int)); //выделение памяти в цикле 10 раз
*p = 0;
>
free(p); //а освобождение вне цикла — однократное. Утечка!

Как избежать ошибок работы с динамической памятью?

  1. Во-первых, быть аккуратным и внимательным.
  2. Во-вторых, если память выделена на одном уровне, освобождение должно быть совершено на том же уровне. Например, если функция выделила память, она же должна ее освободить перед выходом.
    В исключительных ситуациях могут существовать «порождающие» функции, но их нужно «знать в лицо», их имена должны говорить об этом.
    С этой точки зрения пример плохой порождающий функции: стандартная функция Си создания дубликата строки strdup(). По ее имени не очевидно, что при этом выделяется динамическая память, которая обязательно должна быть освобождена.
  3. В-третьих, существуют специальные программные средства, которые позволяют искать утечки памяти, например Valgrind.

Работа с динамической памятью в Си и С++ различается.

Хотя в С++ также в конечном счете используются системные вызовы по выделению и освобождению памяти, они «обернуты» в операторы new и delete. В С++ не рекомендуется использовать механизм malloc() и free() без насущной необходимости обратной совместимости.

История науки и техники Com New

Ссылочный тип данных. Динамическая память. Динамические переменные. Работа с динамической памятью

Статической переменной (статически размещенной) называется описанная явным образом в программе переменная, обращение к ней осуществляется по имени. Место в памяти для размещения статических переменных определяется при компиляции программы. В отличие от таких статических переменных в программах, написанных на языке Pascal, могут быть созданы динамические переменные. Основное свойство динамических переменных заключается в том, что они создаются, и память для них выделяется во время выполнения программы.

Размещаются динамические переменные в динамической области памяти (heap-области). Динамическая переменная не указывается явно в описаниях переменных, и к ней нельзя обратиться по имени. Доступ к таким переменным осуществляется с помощью указателей и ссылок.

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

Зарезервированное слово nil обозначает константу со значением указателя, которая ни на что не указывает.

Приведем пример описания динамических переменных.

Процедуры и функции работы с динамической памятью

Выделяет место в динамической области памяти для размещения динамической переменной p», и ее адрес присваивает указателю p.

2. Процедура Dispose(var p: Pointer).

Освобождает участок памяти, выделенный для размещения динамической переменной процедурой New, и значение указателя p становится неопределенным.

3. Процедура GetMem(var p: Pointer; size: Word).

Выделяет участок памяти в heap-области, присваивает адрес его начала указателю p, размер участка в байтах задается параметром size.

4. Процедура FreeMem(varp: Pointer; size: Word).

Освобождает участок памяти, адрес начала которого определен указателем p, а размер – параметром size. Значение указателя p становится неопределенным.

6. Процедура Release(var p: Pointer) освобождает участок динамической памяти, начиная с адреса, записанного в указатель p процедурой Mark, т. е. очищает ту динамическую память, которая была занята после вызова процедуры Mark.

7. Функция MaxAvail: Longint возвращает длину в байтах самого длинного свободного участка динамической памяти.

8. Функция MemAvail: Longint возвращает полный объем свободной динамической памяти в байтах.

9. Вспомогательная функция SizeOf(X):Word возвращает объем в байтах, занимаемый X, причем X может быть либо именем переменной любого типа, либо именем типа.

ПРОЦЕДУРЫ И ФУНКЦИИ ДЛЯ РАБОТЫ С ДИНАМИЧЕСКОЙ ПАМЯТЬЮ

Читайте также:

  1. I. Объект, предмет и функции курса
  2. II. Взаимосвязь социальной политики и социальной работы
  3. III Функции маркетинга
  4. III. Третья группа профессиональных вредностей возникает вследствие несоблюдения общесанитарных условий в местах работы.
  5. NGN: основные составляющие, технология, важнейшие функции, архитектура.
  6. Telnet – программа работы с удаленным компьютером
  7. VI. Оптимизационные процедуры.
  8. VII. Функции управления
  9. Автоматизированные функции универсальных швейных машин
  10. Агрессивное поведение. Причины. Приемы работы с агрессивными детьми.
  11. АКМЕОЛОГИЧЕСКИЕ ОСНОВЫ САМОСОВЕРШЕНСТВОВАНИЯ ЛИЧНОСТИ: ИХ ПРИРОДА И УСЛОВИЯ РЕАЛИЗАЦИИ. САМООБРАЗОВАНИЕ, ЕГО ИСТОЧНИКИ И ФУНКЦИИ
  12. Анализ ритмичности работы строительно-монтажной организации

ИСПОЛЬЗОВАНИЕ УКАЗАТЕЛЕЙ ДЛЯ ОРГАНИЗАЦИИ СВЯЗАННЫХ СПИСКОВ

Чаще всего указатели используются для ссылки на записи, тем самым достигается значительная экономия памяти. Если же сама запись содержит в себе поле-указатель, указывающий на следующую за ней запись, то это позволяет образовать связанные списки структуру, в которой отдельные записи последовательно связаны друг с другом. В приведенном ниже примере используются записи, в которых наряду с данными об объекте имеется указатель на следующую запись, благодаря чему получается связанный список.

Одно из полей каждого объекта связанного списка имеет тип указатель и указывает на очередной объект в списке. Указатель на первый объект содержится в переменной First, а последний объект имеет указатель на Nil.

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

Функция ADDR. Возвращает результат типа POINTER, в котором содержится адрес аргумента. Обращение:

Здесь Х- любой объект программы (имя любой переменной, процедуры, функции). Возвращаемый адрес совместим с указателем любого типа. Отметим, что аналогичный результат возвращает операция @ .

Функция CSEG. Возвращает значение, хранящееся в регистре CS микропроцессора в начале работы программы в регистре CS содержится сегмент начала кода программы). Обращение:

Результат возвращается в слове типа WORD.

Процедура DISPOSE. Возвращает в кучу фрагмент динамической памяти, который ранее был зарезервирован за типизированным указателем. Обращение:

Здесь ТР — типизированный указатель. При повторном использовании процедуры применительно к уже освобожденному фрагменту возникает ошибка периода исполнения. При освобождении динамических объектов можно указывать вторым параметром обращения к DISPOSE имя деструктора (подробнее см. гл.10).

Функция DSEG. Возвращает значение, хранящееся в регистре DS микропроцессора (в начале работы программы в регистре DS содержится сегмент начала данных программы). Обращение:

Результат возвращается в слове типа WORD.

Процедура FREEMEM. Возвращает в кучу фрагмент динамической памяти, который ранее был зарезервирован за нетипизированным указателем. Обращение:

FREEMEM ( Р, SIZE )

Здесь Р — нетипизированный указатель;

SIZE — длина в байтах освобождаемого фрагмента.

При повторном использовании процедуры применительно к уже освобожденному фрагменту возникает ошибка периода исполнения.

Процедура GETMEM. Резервирует за нетипизированным указателем фрагмент динамической памяти требуемого размера. Обращение:

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

Процедура MARK. Запоминает текущее значение указателя кучи HEAPPTR. Обращение:

Здесь PTR — указатель любого типа, в котором будет возвращено текущее значение HEAPPTR. Используется совместно с процедурой RELEASE для освобождения части кучи.

Функция MAXAVAIL. Возвращает размер в байтах наибольшего непрерывного участка кучи. Обращение:

Результат имеет тип LONGINT. За один вызов процедуры NEW или GETMEM нельзя зарезервировать памяти больше, чем значение, возвращаемое этой функцией.

Функция MEMAVAIL. Возвращает размер в байтах общего свободного пространства кучи. Обращение:

Результат имеет тип LONGINT.

Процедура NEW. Резервирует фрагмент кучи для размещения переменной. Обращение:

Здесь ТР — типизированный указатель.

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

Процедура NEW может вызываться как функция. В этом случае параметром обращения к ней является тип переменной, размещаемой в куче, а функция NEW возвращает значение типа указатель. Например:

При размещении в динамической памяти объекта разрешается в качестве второго параметра обращения к NEW указывать имя конструктора (см. гл.10).

Функция OFS. Возвращает значение типа WORD, содержащее смещение адреса указанного объекта. Вызов:

Здесь Х- выражение любого типа или имя процедуры.

Функция PTR. Возвращает значение типа POINTER по заданному сегменту SEG и смещению OFS. Вызов:

Здесь SEG — выражение типа WORD, содержащее сегмент;

OFS — выражение типа WORD, содержащее смещение.

Значение, возвращаемое функцией, совместимо с указателем любого типа.

Процедура RELEASE. Освобождает участок кучи. Обращение:

Здесь PTR — указатель любого типа, в котором предварительно было сохранено процедурой MARK значение указателя кучи. Освобождается участок кучи от адреса, хранящегося в PTR, до конца кучи. Одновременно уничтожается список всех свободных фрагментов, которые, возможно, были созданы процедурами DISPOSE или FREEMEM.

Функция SEG. Возвращает значение типа WORD, содержащее сегмент адреса указанного объекта. Вызов:

Здесь X — выражение любого типа или имя процедуры.


Функция SIZEOF. Возвращает длину в байтах внутреннего представления указанного объекта. Вызов:

Здесь X — имя переменной, функции или типа. Например, везде в программе из примера вместо константы SIZEOFREAL можно было бы использовать обращение SIZEOF(REAL).

| следующая лекция ==>
|

Дата добавления: 2013-12-12 ; Просмотров: 156 ; Нарушение авторских прав? ;

Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет

Процедуры и функции для работы с динамической памятью.

Здесь Х – любой объект программы. Функция возвращает адрес, совместимый с указателем любого типа. Аналогичный результат дает операция @.

Возвращает текущее значение сегмента CS в начале работы программы – там содержится сегмент начала кода программы.

PROCEDURE DISPOSE(Var P:pointer)

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

Невозможно использование с процедурами MARK и RELEASE.

Существует расширенный синтаксис процедуры, где в качестве второго параметра используется деструктор объекта. Например Dispose(P, Done).

Возвращает текущее значение регистра SS.

Возвращает текущее значение регистра SP.

Возвращает текущее значение регистра DS. В начале работы программы в нем содержится сегмент начала данных программы.

Procedure FREEMEM(Var P:Pointer; Size:Word)

Освобождение динамической памяти размера Size байт, которая была ранее зарезервирована за нетипизированным указателем.

Повторное использование к уже освобожденному фрагменту вызывает ошибку времени исполнения. Эту процедуру нельзя использовать совместно с процедурами Mark и Release в одной программе.

PROCEDURE GETMEM(Var P:Pointer; Size:Word)

Создает новую динамическую переменную размера Size байт и помещает ее адрес в указатель. (Size

Возвращает смещение адреса Х, где Х – выражение любого типа или имя процедуры.

Возвращает значение типа указатель по заданномусегменту (SEG) и смещению (OFS). Значение PTR совместимо с указателем любого типа

RELEASE (Var P:Pointer)

Освобождает динамическую память от места помеченного с помощью процедуры Mark(p) до конца кучи. Нельзя использовать с помощью Freemem и Dispose.

Возвращает сегмент Х, где Х – выражение любого типа или имя процедуры.

Возвращает количество байт, занимаемых аргументом. Когда обращаются к экземпляру объектного типа, который имеет таблицу виртуальных методов, SizeOf возвращает размер, хранящийся в данной таблице.

[АДМИНИСТРАТОР КУЧИ здесь не рассматривается]

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

Для каждого типа данных относящегося к спискам должны быть определены основные операции:

  1. создание списка;
  2. поиск заданного элемента в списке;
  3. вставка элемента;
  4. удаление элемента.

Рассмотрим строки-цепочки символов.

Описание типов. (Здесь для облегчения понимания мы будем использовать в идентификаторах русские буквы, что, конечно, недопустимо в Турбо-паскале).

Процедуры работы с динамическими структурами данных

Лекция 1.2

Динамические структуры данных. Указатели

Основные понятия и определения

Формирование динамических объектов осуществляется с помощью стандартных процедур и функций языка Паскаль.

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

Все объявления данных в разделе их описания (раздел var) требуют точного значения размерности (например, …array[1..10] of …), так как компилятор «распределяет» память для используемой информации до начала выполнения программы и может получить эту размерность только из текста программы (в частности из раздела описания переменных). Такое распределение памяти, до начала выполнения программы, называют статическим.

Распределение памяти в процессе работы программы называют динамическим. Для получения памяти в этом случае в программе необходимо выполнить запрос к операционной системе (ОС). По этому запросу ОС выделяет память в динамической области оперативной памяти компьютера – «куче» (heap) и возвращает программе начальный адрес, выделенного участка оперативной памяти. Доступ к данным, значения которых расположены в выделенной динамической области памяти, требует использования в программе переменной, значением которой и будет возвращаемый ОС адрес. Такая переменная имеет специальный, ссылочный тип данных – указатель.

Формат:

= pointer;

Type

Var

ptr1: T;

ptr2: T1;

Для правильной работы с указателями очень важно четко различатьдва понятия:

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

В приведенном примере это значение переменных ptr1, ptr2

· значение по адресу – значение данных,адрескоторых является значением указателя на эти данные. В программе такие значения обозначаются :

Для данного примера значения по адресу обозначаются так: ptr1^, ptr2^.

Чтобы «почувствовать разницу» между значением указателя и значением данных, адресуемых этим указателем, рассмотрим следующую схему (рисунок ):

После выполнения операции ptrY:= ptrXизменяется значение указателя ptrY и доступ к данным по предыдущему значению этого указателя потерян (данные превращаются в «мусор»)!

После выполнения операции ptrY^:= ptrX^изменяется значение данных по указателю ptrY.Значение указателя ptrY не изменяется!

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

Состояния указателей

Указатели могут иметь 3 состояния:

1. Инициированный указатель. Указатель указывающий на какие-то данные.

2. Пустой указатель. Указатель содержащий пустое значение – NIL. В C/C++ это NULL, уж не знаю почему тут его так назвали. ВАЖНО! Значением NIL нельзя инициировать переменные других типов (не указатели), это приведет к ошибке при компиляции так как само значение NIL имеет тип Pointer!

3. Мусорный указатель. Очень опасный тип указателя. Он содержит какое-то значение, но не указывает никуда. Указатель оказывается в таком состоянии сразу после объявления и после того как память, на которую он указывает, уже освободили. Ошибки, основанные на попытках использования мусорных указателей, доставляют больше всего хлопот. Поэтому рекомендуется своевременно устанавливать неиспользуемым указателям значение NIL и проверять их при использовании на это значение.

Разыменование

Хорошо, указатели указателями, но как использовать данные адресуемые ими? Для этой цели используется операция разыменования — операция обратная объявлению указателя.

…myInt : ^integer;…myInt^ := 1; // присвоим ячейки памяти на которую указывает myInt значение 1 // (не самому указателю!)inc(myInt^); // увеличим значение в ячейке с адресом myInt…

Выражение myInt^ дословно означает следующее: «значение по адресу myInt«. Запомнить несложно: чтобы создать указатель ставим галочку (^) перед типом, чтобы разыменовать — после имени.

Взятие адреса

Важно понимать, что указатель всегда указывает на какой-то байт в памяти, а не на объект (если точнее — на первый байт объекта). И для того, чтобы производить какие-либо действия над объектами или переменными определенных типов, адресуемых указателем, мы должны явно сообщить компилятору тип этих данных.

Мы можем получить адрес абсолютно всего, главное знать, для чего это нужно и каким образом можно с этим работать. Можно даже получить адрес какого либо участка кода и выполнить его. И, естественно, можно получить адреса любых данных в программе. Для этих целей в языке существует специальный оператор – @. Его надо ставить перед именем того объекта, адрес которого нам нужен.

…myPointer_1 := @myIntegerVariable;myPointer_2 := @myStringVariable;myPointer_3 := @myObject;myPointer_4 := @nameOfMyFunction;…

Имена объектов и переменных в этом коде говорят о типе этих объектов. Здесь мы получили адреса переменных (первые 2 строки), объекта какого-то класса (3я строка), какой-то функции (4я строка). Обратите внимание, чтобы получить адрес функции, надо указать её имя без параметров и поставить перед ним @.

Приведение типов

Второй очень важной возможностью, является приведение типов. Приведение типов — это указание компилятору каким образом работать с переменной, непосредственно при её использовании.

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

…myPointer : pointer;myVar : integer;…// пусть myPointer указывает на какое-то целое число (4 байта), тогдаmyVar := integer(myPointer);…

Этот код отлично скомпилируется, но он содержит ошибку. Дело в том, что здесь мы привели сам указатель к целому знаковому числу. Результат в итоге будет не предсказуем. Нужно помнить, что указатель, это тоже переменная в стеке, а операция типа @myPointer вполне легальна. Более того, мы можем использовать в качестве указателя любую переменную размером 4 байта. ( pointer(myDwordValue) ) вполне может послужить указателем. Чтобы приводить именно данные, адресуемые этим указателем необходимо воспользоваться разыменованием:

…myPointer : pointer;myVar : integer;…// пусть myPointer указывает на какое-то целое число (4 байта), тогдаmyVar := integer(myPointer^); // указатель разыменован…

Этот код сделает то, что нам нужно.

Ошибки

Об ошибках связанных с использованием указателей, в кругах программистов ходят легенды. На самом деле все не так страшно. Основной принцип, при работе с указателями — не надеяться на компилятор. Вы должны всегда знать, на что указывает ваш указатель. Если возникают хоть какие-то сомнения, лучше переписать этот участок. Так же стоит всегда держать неиспользуемые указатели инициированными значением NIL и проверять их перед использованием.

Основными ошибками, связанными с использованием указателей, являются утечки памяти, попытки использования неинициированных, пустых или мусорных указателей.

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

Неинициированные или мусорные указатели появляются тогда, когда программист создает указатель или освобождает память, на которую они указывают и не заботится об установке этих указателей в NIL. Еще один момент, часто приводящий к появлению указателей такого типа — это создание двух и более указателей, адресующих одну и ту же область памяти. При освобождении такой памяти, часто, «сбрасываются» не все указатели.

Процедуры работы с динамическими структурами данных

Процедуры New и Dispose


В этих процедурах размер запрашиваемой и освобождаемой памяти явно не указывается в процедуре и определяется типом данных. Поэтому описание указателя должно быть только такого вида: ^ .

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

Выделяемая процедурой New память не инициализируется каким-либо значением.

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

Процедуры работы с динамической памятью

Информатика и информационные технологии: конспект лекций

ЛЕКЦИЯ № 1. Введение в информатику

1. Информатика. Информация. Представление и обработка информации

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

В информатике такое фундаментальное понятие, как информация имеет различные значения:

1) формальное представление внешних форм информации;

2) абстрактное значение информации, ее внутреннее содержание, семантика;

3) отношение информации к реальному миру.

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

Одно из математических описаний информации – это представление ее в виде функции у =f(x, t), где t – время, х – точка некоторого поля, в которой измеряется значение у. В зависимости от параметров функции хи(информацию можно классифицировать.

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

Дискретную информацию обычно отождествляют с цифровой информацией, которая является частным случаем символьной информации алфавитного представления. Алфавит – конечный набор символов любой природы. Очень часто в информатике возникает ситуация, когда символы одного алфавита надо представить символами другого, т. е. провести операцию кодирования. Если количество символов кодируемого алфавита меньше количества символов кодирующего алфавита, то сама операция кодирования не является сложной, в противном случае необходимо использовать фиксированный набор символов кодирующего алфавита для однозначного правильного кодирования.

Как показала практика, наиболее простым алфавитом, позволяющим кодировать другие алфавиты, является двоичный, состоящий из двух символов, которые обозначаются, как правило, через О и 1. С помощью n символов двоичного алфавита можно закодировать 2п символов, а этого достаточно, чтобы закодировать любой алфавит.

Величина, которая может быть представлена символом двоичного алфавита, называется минимальной единицей информации или битом. Последовательность из 8 бит – байт. Алфавит, содержащий 256 различных 8-битных последовательностей, называется байтовым.

В качестве стандартного сегодня в информатике принят код, в котором каждый символ кодируется 1 байтом. Существуют и другие алфавиты.

2. Системы счисления

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

Система счисления называется позиционной, если значение цифры числа зависит от местоположения цифры в числе. В противном случае она называется непозиционной. Значение числа определяется по положению этих цифр в числе.

3. Представление чисел в ЭВМ

32-разрядные процессоры могут работать с оперативной памятью емкостью до 232-1, а адреса могут записываться в диапазоне 00000000 – FFFFFFFF. Однако в реальном режиме процессор работает с памятью до 220-1, а адреса попадают в диапазон 00000 – FFFFF. Байты памяти могут объединяться в поля как фиксированной, так и переменной длины. Словом называется поле фиксированной длины, состоящее из 2 байтов, двойным словом – поле из 4 байтов. Адреса полей бывают четные и нечетные, при этом для четных адресов операции выполняются быстрее.

Числа с фиксированной точкой в ЭВМ представляются как целые двоичные числа, и занимаемый ими объем может составлять 1, 2 или 4 байта.

Целые двоичные числа представляются в дополнительном коде, соответственно числа с фиксированной точкой представляются в дополнительном коде. При этом если число занимает 2 байта, то структура числа записывается по следующему правилу: старший разряд отводится под знак числа, а остальные – под двоичные цифры числа. Дополнительный код положительного числа равен самому числу, а дополнительный код отрицательного числа может быть получен по такой формуле: х = 10и – \х\, где n – разрядность числа.

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

Количество битов мантиссы определяет точность представления чисел, количество битов машинного порядка определяет диапазон представления чисел с плавающей точкой.

4. Формализованное понятие алгоритма

Алгоритм может существовать только тогда, когда в то же самое время существует некоторый математический объект. Формализованное понятие алгоритма связано с понятием рекурсивных функций, нормальных алгоритмов Маркова, машин Тьюринга.

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

Рекурсивные функции – это подкласс вычислимых функций, а алгоритмы, определяющие вычисления, называются сопутствующими алгоритмами рекурсивных функций. Сначала фиксируются базовые рекурсивные функции, для которых сопутствующий алгоритм тривиален, однозначен; затем вводятся три правила – операторы подстановки, рекурсии и минимизации, при помощи которых на основе базовых функций получаются более сложные рекурсивные функции.

Базовыми функциями и их сопутствующими алгоритмами могут выступать:

1) функция n независимых переменных, тождественно равная нулю. Тогда, если знаком функции является φn, то независимо от количества аргументов значение функции следует положить равным нулю;

2) тождественная функция n независимых переменных вида ψni. Тогда, если знаком функции является ψni, то значением функции следует взять значение i-го аргумента, считая слева направо;

3) Λ – функция одного независимого аргумента. Тогда, если знаком функции является λ, то значением функции следует взять значение, следующее за значением аргумента. Разные ученые предлагали свои подходы к формализованному

представлению алгоритма. Например, американский ученый Черч предположил, что класс вычислимых функций исчерпывается рекурсивными функциями и, как следствие, каким бы ни был алгоритм, перерабатывающий один набор целых неотрицательных чисел в другой, найдется алгоритм, сопутствующий рекурсивной функции, эквивалентный данному. Следовательно, если для решения некоторой поставленной задачи нельзя построить рекурсивную функцию, то и не существует алгоритма для ее решения. Другой ученый, Тьюринг, разработал виртуальную ЭВМ, которая перерабатывала входную последовательность символов в выходную. В связи с этим им был выдвинут тезис, что любая вычислимая функция вычислима по Тьюрингу.

ЛЕКЦИЯ № 2. Язык Pascal

1. Введение в язык Pascal

Основные символы языка – буквы, цифры и специальные символы – составляют его алфавит. Язык Pascal включает следующий набор основных символов:

Процедуры работы с динамической памятью

При создании массива с фиксированными размерами под него выделяется определенная память. Например, пусть у нас будет массив с пятью элементами:

Для такого массива выделяется память 5 * 8 (размер типа double) = 40 байт. Таким образом, мы точно знаем, сколько в массиве элементов и сколько он занимает памяти. Однако это не всегда удобно. Иногда бывает необходимо, чтобы количество элементов и соответственно размер выделяемой памяти для массива определялись динамически в зависимости от некоторых условий. Например, пользователь сам может вводить размер массива. И в этом случае для создания массива мы можем использовать динамическое выделение памяти.

Для управления динамическим выделением памяти используется ряд функций, которые определены в заголовочном файле stdlib.h :

malloc() . Имеет прототип

Выделяет память длиной в s байт и возвращает указатель на начало выделенной памяти. В случае неудачного выполнения возвращает NULL

calloc() . Имеет прототип

Выделяет память для n элементов по m байт каждый и возвращает указатель на начало выделенной памяти. В случае неудачного выполнения возвращает NULL

realloc() . Имеет прототип

Изменяет размер ранее выделенного блока памяти, на начало которого указывает указатель bl, до размера в ns байт. Если указатель bl имеет значение NULL , то есть память не выделялась, то действие функции аналогично действию malloc

free() . Имеет прототип

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

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

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

Консольный вывод программы:

Здесь для управления памятью для массива определен указатель block типа int . Количество элементов массива заранее неизвестно, оно представлено переменной n.

Вначале пользователь вводит количество элементов, которое попадает в переменную n. После этого необходимо выделить память для данного количества элементов. Для выделения памяти здесь мы могли бы воспользоваться любой из трех вышеописанных функций: malloc, calloc, realloc. Но конкретно в данной ситуации воспользуемся функцией malloc :

Прежде всего надо отметить, что все три выше упомянутые функции для универсальности возвращаемого значения в качестве результата возвращают указатель типа void * . Но в нашем случае создается массив типа int, для управления которым используется указатель типа int * , поэтому выполняется неявное приведение результата функции malloc к типу int * .

В саму функцию malloc передается количество байтов для выделяемого блока. Это количество подсчитать довольно просто: достаточно умножить количество элементов на размер одного элемента n * sizeof(int) .

После выполнения всех действий память освобождается с помощью функции free() :

Важно, что после выполнения этой функции мы уже не сможем использовать массив, например, вывести его значения на консоль:

И если мы попытаемся это сделать, то получим неопределенные значения.

Вместо функции malloc аналогичным образом мы могли бы использовать функцию calloc() , которая принимает количество элементов и размер одного элемента:

Либо также можно было бы использовать функцию realloc() :

При использовании realloc желательно (в некоторых средах, например, в Visual Studio, обязательно) инициализировать указатель хотя бы значением NULL.

Но в целом все три вызова в данном случае имели бы аналогичное действие:

Теперь рассмотрим более сложную задачу — динамическое выделение памяти для двухмерного массива:

Переменная table представляет указатель на массив указателей типа int* . Каждый указатель table[i] в этом массиве представляет указатель на подмассив элементов типа int , то есть отдельные строки таблицы. А переменная table фактически представляет указатель на массив указателей на строки таблицы.

Для хранения количества элементов в каждом подмассиве определяется указатель rows типа int . Фактически он хранит количество столбцов для каждой строки таблицы.

Сначала вводится количество строк в переменную rowscount . Количество строк — это количество указателей в массиве, на который указывает указатель table . И кроме того, количество строк — это количество элементов в динамическом массиве, на который указывает указатель rows . Поэтому вначале необходимо для всех этих массивов выделить память:

Далее в цикле осуществляется ввод количества столбцов для каждый строки. Введенное значение попадает в массив rows. И в соответствии с введенным значением для каждой строки выделяется необходимый размер памяти:

Затем производится ввод элементов для каждой строки.

В конце работы программы при выводе происходит освобождение памяти. В программе память выделяется для строк таблицы, поэтому эту память надо освободить:

И кроме того, освобождается память, выделенная для указателей table и rows:

16. Ссылочный тип данных. Динамическая память. Динамические переменные. Работа с динамической памятью

16. Ссылочный тип данных. Динамическая память. Динамические переменные. Работа с динамической памятью

Статической переменной (статически размещенной) называется описанная явным образом в программе переменная, обращение к ней осуществляется по имени. Место в памяти для размещения статических переменных определяется при компиляции программы. В отличие от таких статических переменных в программах, написанных на языке Pascal, могут быть созданы динамические переменные. Основное свойство динамических переменных заключается в том, что они создаются, и память для них выделяется во время выполнения программы.

Размещаются динамические переменные в динамической области памяти (heap-области). Динамическая переменная не указывается явно в описаниях переменных, и к ней нельзя обратиться по имени. Доступ к таким переменным осуществляется с помощью указателей и ссылок.

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

Зарезервированное слово nil обозначает константу со значением указателя, которая ни на что не указывает.

Приведем пример описания динамических переменных.

Процедуры и функции работы с динамической памятью

Выделяет место в динамической области памяти для размещения динамической переменной p», и ее адрес присваивает указателю p.

2. Процедура Dispose(var p: Pointer).


Освобождает участок памяти, выделенный для размещения динамической переменной процедурой New, и значение указателя p становится неопределенным.

3. Процедура GetMem(var p: Pointer; size: Word).

Выделяет участок памяти в heap-области, присваивает адрес его начала указателю p, размер участка в байтах задается параметром size.

4. Процедура FreeMem(varp: Pointer; size: Word).

Освобождает участок памяти, адрес начала которого определен указателем p, а размер – параметром size. Значение указателя p становится неопределенным.

6. Процедура Release(var p: Pointer) освобождает участок динамической памяти, начиная с адреса, записанного в указатель p процедурой Mark, т. е. очищает ту динамическую память, которая была занята после вызова процедуры Mark.

7. Функция MaxAvail: Longint возвращает длину в байтах самого длинного свободного участка динамической памяти.

8. Функция MemAvail: Longint возвращает полный объем свободной динамической памяти в байтах.

9. Вспомогательная функция SizeOf(X):Word возвращает объем в байтах, занимаемый X, причем X может быть либо именем переменной любого типа, либо именем типа.

Урок №85. Динамическое выделение памяти

Обновл. 15 Июл 2020 |

С++ поддерживает три основных типа выделения (или ещё «распределения») памяти, с двумя из которых, мы уже знакомы:

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

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

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

Динамическое выделение переменных

Как статическое, так и автоматическое распределение памяти имеют два общих свойства:

Размер переменной/массива должен быть известен во время компиляции.

Выделение и освобождение памяти происходит автоматически (когда переменная создаётся/уничтожается).

В большинстве случаев с этим всё ОК. Однако, когда дело доходит до работы с пользовательским вводом, то эти ограничения могут привести к проблемам.

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

Если нам нужно объявить размер всех переменных во время компиляции, то самое лучшее, что мы можем сделать — это попытаться угадать их максимальный размер, надеясь, что этого будет достаточно:

Это плохое решение, по крайней мере, по трём причинам:

Во-первых, теряется память, если переменные фактически не используются или используются, но не все. Например, если мы выделим 30 символов для каждого имени, но имена в среднем будут занимать по 15 символов, то потребление памяти получится в два раза больше, чем нам нужно на самом деле. Или рассмотрим массив rendering : если он использует только 20 000 полигонов, то память для других 20 000 полигонов фактически тратится впустую (т.е. не используется)!

Во-вторых, память для большинства обычных переменных (включая фиксированные массивы) выделяется из специального резервуара памяти — стека. Объём памяти стека в программе, как правило, невелик: в Visual Studio он по умолчанию равен 1МБ. Если вы превысите это значение, то произойдёт переполнение стека, и операционная система автоматически завершит выполнение вашей программы.

В Visual Studio это можно проверить, запустив следующий фрагмент кода:

Лимит в 1МБ памяти может быть проблематичным для многих программ, особенно где используется графика.

В-третьих, и самое главное, это может привести к искусственным ограничениям и/или переполнению массива. Что произойдёт, если пользователь попытается прочесть 500 записей с диска, но мы выделили память максимум для 400? Либо мы выведем пользователю ошибку, что максимальное количество записей — 400, либо (в худшем случае) выполнится переполнение массива и затем что-то очень нехорошее.

К счастью, эти проблемы легко устраняются с помощью динамического выделения памяти. Динамическое выделение памяти — это способ запроса памяти из операционной системы запущенными программами по надобности. Эта память не выделяется из ограниченной памяти стека программы, а из гораздо большего хранилища, управляемого операционной системой — кучи. На современных компьютерах размер кучи может составлять гигабайты памяти.

Для динамического выделения памяти для одной переменной используется оператор new:

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

Для доступа к выделенной памяти создаётся указатель:

Затем мы можем разыменовать указатель для получения значения:

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

Как работает динамическое выделение памяти?

На вашем компьютере имеется память (возможно, большая её часть), которая доступна для использования программами. При запуске программы ваша операционная система загружает эту программу в некоторую часть этой памяти. И эта память, используемая вашей программой, разделена на несколько частей, каждая из которых выполняет определённую задачу. Одна часть содержит ваш код, другая используется для выполнения обычных операций (отслеживание вызываемых функций, создание и уничтожение глобальных и локальных переменных и т.д.). Мы поговорим об этом позже. Тем не менее, большая часть доступной памяти просто находится там, ожидая запросов на выделение от программ.

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

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

Освобождение памяти

Когда вы динамически выделяете переменную, то вы также можете её инициализировать посредством прямой инициализации или uniform инициализации (в С++11):

Когда уже всё, что нужно было, выполнено с динамически выделенной переменной — нужно явно указать С++ освободить эту память. Для переменных это выполняется с помощью оператора delete:

Оператор delete на самом деле ничего не удаляет. Он просто возвращает память, которая была выделена ранее, обратно в операционную систему. Затем операционная система может переназначить эту память другому приложению (или этому же снова).

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

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

Висячие указатели

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

Указатель, указывающий на освобождённую память, называется висячим указателем. Разыменование или удаление висячего указателя приведёт к неожиданным результатам. Рассмотрим следующую программу:

В программе выше значение 8, которое ранее было присвоено динамической переменной, после освобождения может и далее находиться там, а может и нет. Также возможно, что освобождённая память уже могла быть выделена другому приложению (или для собственного использования операционной системы), и попытка доступа к ней приведёт к тому, что операционная система автоматически прекратит выполнение вашей программы.

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

Есть несколько рекомендаций, которые могут здесь помочь.

Во-первых, старайтесь избегать ситуаций, когда несколько указателей указывают на одну и ту же часть выделенной памяти. Если это невозможно, то проясните, какой указатель из всех «владеет» памятью (и отвечает за её удаление), а какие указатели просто получают доступ к ней.

Во-вторых, когда вы удаляете указатель, и, если он не выходит из области видимости сразу же после удаления, то его нужно сделать нулевым, т.е. присвоить значение 0 (или nullptr в С++11). Под «выходом из области видимости сразу же после удаления» имеется в виду, что вы удаляете указатель в самом конце блока, в котором он объявлен.

Правило: Присваивайте удалённым указателям значение 0 (или nullptr в C++11), если они не выходят из области видимости сразу же после удаления.

Оператор new

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

По умолчанию, если оператор new не сработал, память не выделилась, то генерируется исключение bad_alloc. Если это исключение будет неправильно обработано (а именно так и будет, поскольку мы ещё не рассматривали исключения и их обработку), то программа просто прекратит своё выполнение (произойдёт сбой) с ошибкой необработанного исключения.

Во многих случаях процесс генерации исключения оператором new (как и сбой программы) нежелателен, поэтому есть альтернативная форма оператора new, которая возвращает нулевой указатель, если память не может быть выделена. Нужно просто добавить константу std::nothrow между ключевым словом new и типом данных:

В примере выше, если new не возвратит указатель с динамически выделенной памятью, то возвратится нулевой указатель.

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

Поскольку не выделение памяти оператором new происходит крайне редко, то обычно программисты забывают выполнять эту проверку!

Нулевые указатели и динамическое выделение памяти

Нулевые указатели (указатели со значением 0 или nullptr) особенно полезны в процессе динамического выделения памяти. Их наличие как бы сообщаем нам: «Этому указателю не выделено никакой памяти». А это, в свою очередь, можно использовать для выполнения условного выделения памяти:

Удаление нулевого указателя ни на что не влияет. Таким образом, в следующем нет необходимости:

Вместо этого вы можете просто написать:

Если ptr не является нулевым, то динамически выделенная переменная будет удалена. Если значением указателя является нуль, то ничего не произойдёт.

Утечка памяти

Динамически выделенная память не имеет области видимости, т.е. она остаётся выделенной до тех пор, пока не будет явно освобождена или пока ваша программа не завершит своё выполнение (и операционная система очистит все буфера памяти самостоятельно). Однако указатели, используемые для хранения динамически выделенных адресов памяти, следуют правилам области видимости обычных переменных. Это несоответствие может вызвать интересное поведение. Например:

Здесь мы динамически выделяем целочисленную переменную, но никогда не освобождаем память через delete. Поскольку указатели следуют всем тем же правилам, что и обычные переменные, то, когда функция завершит своё выполнение, ptr выйдет из области видимости. Поскольку ptr — это единственная переменная, хранящая адрес динамически выделенной целочисленной переменной, то, когда ptr уничтожится — больше не останется указателей на динамически выделенную память. Это означает, что программа «потеряет» адрес динамически выделенной памяти. И, в результате, эту динамически выделенную целочисленную переменную нельзя будет удалить.

Это называется утечкой памяти. Утечка памяти происходит, когда ваша программа теряет адрес некоторой динамически выделенной части памяти (например, переменной или массива), прежде чем вернуть её обратно в операционную систему. Когда это происходит, то программа уже не может удалить эту динамически выделенную память, поскольку она больше не знает, где она находится. Операционная система также не может использовать эту память, поскольку считается, что она по-прежнему используется вашей программой.

Утечки памяти съедают свободную память во время выполнения программы, уменьшая количество доступной памяти не только для этой программы, но и для других программ также. Программы с серьёзными проблемами с утечкой памяти могут съесть всю доступную память, в результате чего весь ваш компьютер будет медленнее работать или даже произойдёт сбой. Только после того, как выполнение вашей программы завершится, операционная система сможет очистить и вернуть всю память, которая «утекла».

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

История науки и техники Com New

Ссылочный тип данных. Динамическая память. Динамические переменные. Работа с динамической памятью

Статической переменной (статически размещенной) называется описанная явным образом в программе переменная, обращение к ней осуществляется по имени. Место в памяти для размещения статических переменных определяется при компиляции программы. В отличие от таких статических переменных в программах, написанных на языке Pascal, могут быть созданы динамические переменные. Основное свойство динамических переменных заключается в том, что они создаются, и память для них выделяется во время выполнения программы.

Размещаются динамические переменные в динамической области памяти (heap-области). Динамическая переменная не указывается явно в описаниях переменных, и к ней нельзя обратиться по имени. Доступ к таким переменным осуществляется с помощью указателей и ссылок.

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

Зарезервированное слово nil обозначает константу со значением указателя, которая ни на что не указывает.

Приведем пример описания динамических переменных.

Процедуры и функции работы с динамической памятью

Выделяет место в динамической области памяти для размещения динамической переменной p», и ее адрес присваивает указателю p.

2. Процедура Dispose(var p: Pointer).

Освобождает участок памяти, выделенный для размещения динамической переменной процедурой New, и значение указателя p становится неопределенным.

3. Процедура GetMem(var p: Pointer; size: Word).

Выделяет участок памяти в heap-области, присваивает адрес его начала указателю p, размер участка в байтах задается параметром size.

4. Процедура FreeMem(varp: Pointer; size: Word).

Освобождает участок памяти, адрес начала которого определен указателем p, а размер – параметром size. Значение указателя p становится неопределенным.

6. Процедура Release(var p: Pointer) освобождает участок динамической памяти, начиная с адреса, записанного в указатель p процедурой Mark, т. е. очищает ту динамическую память, которая была занята после вызова процедуры Mark.

7. Функция MaxAvail: Longint возвращает длину в байтах самого длинного свободного участка динамической памяти.

8. Функция MemAvail: Longint возвращает полный объем свободной динамической памяти в байтах.

9. Вспомогательная функция SizeOf(X):Word возвращает объем в байтах, занимаемый X, причем X может быть либо именем переменной любого типа, либо именем типа.

Илон Маск рекомендует:  @page в CSS
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL
Читайте также:
  1. C.) . воспитания — прерогатива государства, которая под влиянием науки и общества формирует ее как главный компонент педагогической работы
  2. I) обеспечения того, чтобы процедуры, помещения и материалы для голосования были подходящими, доступными и легкими для понимания и использования;
  3. I. Выполнение контрольной работы
  4. I. Выполнение контрольной работы
  5. I. Задания для самостоятельной работы
  6. I. Задания для самостоятельной работы
  7. I. Задания для самостоятельной работы
  8. I. Задания для самостоятельной работы
  9. I. Задания для самостоятельной работы
  10. I. ОБЩИЕ ПОЛОЖЕНИЯ ПО ВЫПОЛНЕНИЮ КОНТРОЛЬНОЙ РАБОТЫ