Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Подбельский Фомин_Программирование на языке СИ_...doc
Скачиваний:
234
Добавлен:
10.08.2019
Размер:
53.81 Mб
Скачать

Массивы динамической памяти.

Массивы динамической памяти. В соответствии со стандартом языка массив представляет собой совокупность элементов, каждый из которых имеет одни и те же атрибуты (характеристики). Все элементы размещаются в смежных участках памяти подряд, начиная с адреса, соответствующего началу массива, т.е. значению & имя_массива [0].

При традиционном определении массива:

тип имя_массива [количество_элементов];

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

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

Формирование массивов с переменными размерами можно организовать с помощью указателей и средств для динамического выделения памяти. Начнем рассмотрение указанных средств с библиотечных функций, описанных в заголовочных файлах ailoc.h и stdlib.h стандартной библиотеки (файл alloc.h не является стандартным). В табл. 4.1 приведены сведения об этих библиотечных функциях. Функции malloc( ), calloc( ) и realloc( ) динамически выделяют память в соответствии со значениями параметров и возвращают адрес начала выделенного участка памяти. Для универсальности тип возвращаемого значения каждой из этих функций есть void*. Этот указатель (указатель такого типа) можно преобразовать к указателю любого типа с помощью операции явного приведения типа (тип *).

Функция free() решает обратную задачу - освобождает память, выделенную перед этим с помощью одной из трех функций calloc(), malloc( ) или realloc(). Сведения об этом участке памяти передаются в функцию free() с помощью указателя - параметра типа void *. Преобразование указателя любого типа к типу void * выполняется автоматически, поэтому вместо формального параметра void *bl можно подставить в качестве фактического параметра указатель любого типа без операции явного приведения типов.

Таблица 4.1

Функции для выделения и освобождения памяти

Функция

Прототип и краткое описание

malloc

void * malloc (unsigned s);

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

calioc

void * calioc (unsigned n, unsigned m);

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

realloc

void * realloc (void *bl, unsigned ns);

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

free

void * free (void *bl);

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

Следующая программа иллюстрирует на несложной задаче особенности применения функций выделения (malloc) и освобождения (free) динамической памяти. Решается следующая задача: ввести и напечатать в обратном порядке набор вещественных чисел, количество которых заранее не фиксировано, а вводится до начала ввода самих числовых значений. Текст программы может быть таким:

Результат выполнения программы (компилятор ВС++3.1 и компилятор из операционной системы Free BSD UNIX):

В программе int n - количество вводимых чисел типа float, t - указатель на начало области, выделяемой для размещения n вводимых чисел. Указатель t принимает значение адреса области, выделяемой для n штук значений типа float. Обратите внимание на приведение типа (float*) значения, возвращаемого функцией malloc( ). Доступ к участкам выделенной области памяти выполняется с помощью операции индексирования: t[i] и t[i-l]. Остальное очевидно из текста программы. Оператор free(t); содержит вызов функции, освобождающей выделенную ранее динамическую память и связанной с указателем t.

Следующее замечание не имеет никакого отношения к стандартам языка Си. Выше указано, что результат выполнения программы получен при использовании компилятора языка Си, включенного в интегрированную среду программирования Borland C++3.1 и компилятора из операционной системы Free BSD UNIX. Необходимость в этом замечании связана с давним затруднением, которое сопровождает уже несколько поколений компиляторов разных фирм. Функция scanf( ) "отказывается" вводить значения индексированных элементов динамических вещественных массивов. Например, при попытке выполнить программу в интегрированной среде Turbo C++1.01 выдается сообщение:

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

Во-вторых, можно воспользоваться компилятором, библиотечная функция которого не выдает такой ошибки. Такой компилятор языка Си включен в интегрированную среду Borland C++ 3.1. (Однако и этот улучшенный компилятор "спотыкается" при вводе значений элементов многомерных массивов динамической памяти, которые мы рассмотрим ниже в этом параграфе.) Устойчиво работает компилятор в системе Free BSD UNIX.

Массивы в каноническом смысле, вводимые с помощью определения массива, отличаются от массивов динамической памяти. Наиболее существенным отличием является отсутствие "имени собственного" у динамического массива. Чтобы продемонстрировать важность этого различия, рассмотрим стандартную процедуру определения количества элементов канонического массива с помощью операции sizeof.

Напомним, что операция sizeof имеет две формы вызова:

sizeof (тип)

sizeof выражение

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

sizeof (long) обычно равно 4;

sizeof (long double) обычно равно 10.

Чтобы в программе вычислить количество элементов канонически определенного конкретного массива, удобно использовать операцию sizeof, применяя ее дважды - к имени массива и к его нулевому элементу. Если операндом для sizeof служит имя массива (без индексов), то возвращается значение "длины" памяти, отведенной для массива в целом. Таким образом, значением выражения

sizeof (имя_массива) /sizeof (имя_массива[0])

будет количество элементов в массиве.

В следующем фрагменте "перебираются" все элементы массива х:

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

Независимо от значения параметра функции malloc( ) значение выражения sizeof(pointer) всегда будет одинаковым - равным величине участка памяти для переменной (для указателя) pointer.