1. Средства для работы с памятью в языке С++: указатели; базовые понятия; адресная арифметика.

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

Указателем называется переменная, которая содержит значение адреса элемента памяти, где хранится значение другой переменной. Главная операция над указателями - это косвенное обращение (разыменование), т.е. обращение к объекту, на который настроен указатель. Эту операцию обычно называют просто косвенностью. Операция косвенности * является префиксной унарной операцией. Например:

char c1 = 'a';

char* p = &c1; // p содержит адрес c1

char c2 = *p;// c2 = 'a'

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

char A[20] ={0x11, 0x15, 0x32, 0x16, 0x44, 0x1, 0x6, 0x8A};

char * p; int *q; long *l;

p = A; //имя массива - указатель

q = (int*) p;

l = (long*) p;

p[2] = 5; /* записать 5 во второй байт области A */

q[1] = 7; /*записать 7 в первое слово области A*/

Здесь p - указатель на область байтов, q - на область целых, l - на область длинных целых. Соответственно операции адресной арифметики *(p+i), *(q+i), *(l+i) или p[i], q[i], l[i] адресуют i-ый байт, i-ое целое и i-ое длинное целое от начала области. Область памяти имеет различную структуру (байтовую, словную и т.д.) в зависимости от того, через какой указатель мы с ней работаем. При этом неважно, что сама область определена как массив типа char - это имеет отношение только к операциям с использованием идентификатора массива.

Присваивание значения указателя одного типа указателю другого типа сопровождается действием, которое называется в Си преобразованием типа указателя , и которое в Си++ обозначается всегда явно. Операция (int*)p меняет в текущем контексте тип указателя char* на int*. На самом деле это действие является чистой фикцией (команды транслятором не генерируются). Транслятор просто запоминает, что тип указуемой переменной изменился и операции адресной арифметики и косвенного обращения нужно выполнять с учетом нового типа указателя.

Явное преобразование типа указателя в выражении. Преобразование типа указателя можно выполнить не только при присваивании, но и внутри выражения, "на лету". В этом случае текущий указатель меняет тип указываемого элемента только в цепочке выполняемых операций. char A[20]; ((int *)A )[2] = 5;

Имя массива A - указатель на его начало - имеет тип char*, который явно преобразуется в int* . Тем самым в текущем контексте мы ссылаемся на массив как на область целых переменных. Применительно к указателю на массив целых выполняется операция индексации и последующее присваивание. Результат : целое 5 записывается во второй элемент целого массива, размещенного в А .

Операция *p++ применительно к любому указателю интерпретируется как " взять указываемую переменную и перейти к следующей" , следовательно, значением указателя после выполнения операции будет адрес переменной, следующей за выбранной. Использование такой операции в сочетании с явным преобразованием типа позволяет извлекать или записывать переменные различных типов, последовательно расположенных в памяти.

char A[20], *p=A; *p++ = 5; /*Записать в массив байт с кодом 5*/ *((int* )p)++ = 5; /*Записать в массив целое 5*/ *((double*)p)++ = 5.5; /*Записать в массив вещественное 5.5*/

Адресная арифметика

Вычитание указателей друг из друга определено, только если они одного и того же типа. Результатом будет кол-во элементов (целое число) данного типа между ними.

Результат применения к указателям арифметических операций +, -, ++ или -- зависит от типа объекта, на который они указывают. Когда к указателю p типа T* применяется арифметическая операция, предполагается, что p указывает на элемент вектора объектов типа T; p+1 означает следующий элемент этого вектора, а p-1 - предыдущий элемент. Отсюда следует, что значение p+1 будет на sizeof(T) больше значения p.

Gри определенных условиях указатели можно сравнивать. Если p и q указывают на элементы одного и того же массива, то такие отношения, как <, >= и т.д., работают надлежащим образом. Например, p < q истинно, если p указывает на более ранний элемент массива, чем q. Отношения == и != тоже работают. Любой указатель можно осмысленным образом сравнить на равенство или неравенство с null. Но ни за что нельзя ручаться, если вы используете сравнения при работе с указателями, указывающими на разные массивы. Если вам повезет, то на всех машинах вы получите очевидную бессмыслицу. Если же нет, то ваша программа будет правильно работать на одной машине и давать непостижимые результаты на другой.

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

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

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

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

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

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

union ptr { int *p; double *d; long *l; } PTR;

int A[100];

PTR.p=A;

*(PTR.p)++ =5;

*(PTR.l)++ =5;

*(PTR.d)++=5.56;

Hosted by uCoz