|
|||||||||||||||||||||||||||||||
АвтоАвтоматизацияАрхитектураАстрономияАудитБиологияБухгалтерияВоенное делоГенетикаГеографияГеологияГосударствоДомДругоеЖурналистика и СМИИзобретательствоИностранные языкиИнформатикаИскусствоИсторияКомпьютерыКулинарияКультураЛексикологияЛитератураЛогикаМаркетингМатематикаМашиностроениеМедицинаМенеджментМеталлы и СваркаМеханикаМузыкаНаселениеОбразованиеОхрана безопасности жизниОхрана ТрудаПедагогикаПолитикаПравоПриборостроениеПрограммированиеПроизводствоПромышленностьПсихологияРадиоРегилияСвязьСоциологияСпортСтандартизацияСтроительствоТехнологииТорговляТуризмФизикаФизиологияФилософияФинансыХимияХозяйствоЦеннообразованиеЧерчениеЭкологияЭконометрикаЭкономикаЭлектроникаЮриспунденкция |
Адресная арифметика языка СиЯзык Си имеет средства работы непосредственно с областями оперативной памяти ЭВМ, задаваемыми их адресами (указателями). В языке C указатели строго типизированы, т. е. различают указатели (адреса) символьных, целых, вещественных величин, а также типов данных, создаваемых программистом. Для описания указателя на какой-либо тип данных перед именем переменной ставится *. Например в строке int *a, *b, c, d; описываются два указателя и две переменные целого типа. В строке double *bc; описан указатель вещественного типа. Никогда не следует писать знак * слитно с типом данных, например как в следующей строке: int* a, b; В этой строке создается ложное впечатление о том, что описаны два указателя на тип int, в то время как на самом деле описан один указатель на int, а именно a, и одна переменная типа int. Описание переменных заставляет компилятор выделять память для хранения этих переменных. Описание указателя выделяет память лишь для хранения адреса. В этом смысле указатель на целое данное и на тип double будут занимать в ЭВМ одинаковое количество байт памяти, зависящее от модели памяти, на которую настроен компилятор. При описании указателей в качестве имени типа данных можно использовать ключевое слово void, например void *vd; При таком описании с указателем не связывается никакой тип данных, то есть получаем указатель на данное произвольного типа. Для указателей одного и того же типа допустимой является операция присваивания, кроме того указателю типа void может быть присвоено значение адреса данного любого типа, но не наоборот, например int *a, *b; double *d; void *v; ... a = b; /* Правильно */ v = a; /* Правильно */ v = d; /* Правильно */ b = v; /* Неправильно */ d = a; /* Неправильно */ В случае неправильного присваивания указателей компиляторы обычно выдают предупреждающие сообщения, которыми никогда не следует пренебрегать. Если по какой-либо причине необходимо выполнить операцию присваивания между указателями разного типа, то следует использовать явное преобразование типов, например для указателей из предыдущего примера b = (int *) v; d = (double *) a; При этом ответственность за корректность подобных операций целиком ложится на программиста. Действительно, в предыдущем примере a является указателем на ячейку памяти для хранения величины типа int. После присваивания указателей с явным преобразования типов, делается возможным обращение к этой ячейке посредством указателя d, как к ячейке с величиной типа double. Размер этого типа обычно 8 байт, да и внутреннее представление данных в корне отличается от типа int. Никакого преобразования самих данных не делается, ведь речь идет только об указателях. Дальнейшая работа с указателем d скорее всего заденет байты, соседние с байтами на которые указывает a. Результат интерпретации этих байт будет тоже неверным. Для поддержки адресной арифметики в языке Си имеются две специальные операции - операция взятия адреса & и операция получения значения по заданному адресу * (операция разадресации). Операция & может применяться к любому объекту программы, адрес которого в принципе может быть определен. Результатом операции является указатель того же типа, что и тип объекта, имеющий значение адреса объекта. Если эту операцию применить к указателю, то результатом будет адрес ячейки памяти, в которой хранится значение указателя. Результат операции & можно использовать в любом выражении, где допускается использование указателя соответствующего типа. Операция * может применяться только к указателям и только в том случае, когда они типизированы. При необходимости применить эту операцию к указателю типа void следует использовать явное преобразование типов. Результатом операции * является значение того объекта, к адресу которого применялась операция *, тип результата совпадает с типом объекта. Результат операции * можно использовать в любом выражении, где допускается использование объекта соответствующего типа. Рассмотрим работу вышеописанных операций на следующем примере int *p, a, b; double d; void *pd; p = &a; *p = 12; p = &b; *p = 20; /* Здесь a содержит число 12, b - число 20 */ pd = &d; *((double *) pd) = a; /* Здесь d содержит число 12.0 */ Поясним первые четыре присваивания рисунком, в котором прямоугольники изображают ячейки памяти для хранения величин типа int и указателей, внутри которых проставлены значения величин, а над ними записаны их названия и адреса. Состояние ячеек до первого присваивания
Состояние ячеек после присваивания p = &a
Состояние ячеек после присваивания *p = 12
Состояние ячеек после присваивания p = &b
Состояние ячеек после присваивания *p = 20
Описание указателя не является требованием на выделение памяти для хранения данных. Память выделяется только для хранения адреса. Поэтому прежде, чем использовать указатель, ему нужно присвоить значение адреса реально существующего объекта. В противном случае результат работы программы непредсказуем. Рассмотрим, например, следующую последовательность строк double *a, b; b = *a; *a = 135.7; В этой последовательности используется указатель, которому предварительно не присвоено никакого значения. В ячейке a находится произвольное значение, возможно оставшееся от работы предыдущей программы. Первая операция присваивания приведет к тому, что переменная b получит значение из ячейки памяти с непредсказуемым адресом. Вторая - к тому, что по непредсказуемому адресу будут записаны 8 байт, являющиеся двоичным представлением числа 135.7. Если эти байты попадут на область данных программы, то программа, скорее всего, выдаст неправильный результат. Если они попадут на область кода программы или на системную область MS DOS, то в лучшем случае программа аварийно завершится, а в худшем компьютер полностью зависнет. Если делается попытка присвоить какое-либо значение по адресу указателя, значение которого равно нулю, то многие компиляторы выдают сообщение Null pointer assingment К сожалению, это сообщение выдается уже после того, как программа завершилась, если она смогла завершиться. Следует также опасаться случая, когда указатель содержит адрес объекта программы, завершившего свое существование. Например, результат работы следующей программы неверен и непредсказуем:
#include <stdio.h> #include <math.h> double * Cube(double x) { double cube_val; cube_val = x*x*x; return &cube_val; } void main(void) { double *py; py = Cube(5); printf("y1 = %lf\n", *py); sin(0.7); printf("y1 = %lf\n", *py); }
Это происходит потому, что функция Cube() возвращает указатель на локальную переменную cube_val, которая существует только в пределах функции Cube(). После возврата из функции память, ранее распределенная под переменную cube_val, освобождается и может быть использована компилятором для других целей несмотря на то, что значение возвращенного указателя по-прежнему содержит ее адрес. Операция получения адреса (&) и раскрытия ссылки (*) С помощью указателей можно быстро обратиться к элементам массива или дать функции возможность модифицировать свои аргументы. Указатели широко используются для связи элементов в списках, в двоичных деревьях и в других динамических структурах данных. Первый из них — оператор &, это унарный оператор, возвращающий адрес операнда в памяти. Например, оператор m = &count; записывает в переменную m адрес переменной count. Этот адрес представляет собой адрес ячейки памяти компьютера, в которой размещена переменная. Адрес и значение переменной — совершенно разные понятия. Выражение "&переменная" означает "адрес переменной". Следовательно, инструкция m = &scount; означает: "Переменной m присвоить адрес, по которому расположена переменная count;". Допустим, переменная count расположена в памяти в ячейке с адресом 2000, а ее значение равно 100. Тогда в предыдущем примере переменной m будет присвоено значение 2000. Второй рассматриваемый оператор * является двойственным (дополняющим) по отношению к &. Оператор * является унарным оператором, он возвращает значение объекта, расположенного по указанному адресу. Операндом для * служит адрес объекта (переменной). Например, если переменная m содержит адрес переменной count, то оператор q = *m;записывает значение переменной count в переменную q. В нашем примере переменная q получит значение 100, потому что по адресу 2000 записано число 100, причем этот адрес записан в переменной m. Выражение "* адрес" означает "по адресу". Наш фрагмент программы можно прочесть как "q получает значение, расположенное по адресу m". К сожалению, символ операции раскрытия ссылки совпадает с символом операции умножения, а символ операции получения адреса — с символом операции поразрядного И. Необходимо помнить, что эти операторы не имеют никакого отношения друг к другу. Операторы * и & имеют более высокий приоритет, чем любая арифметическая операция, кроме унарного минуса, имеющего такой же приоритет. Если переменная является указателем, то в объявлении перед ее именем нужно поставить символ *, он сообщит компилятору о том, что это указатель на переменную данного типа. Например, объявление указателя на переменную типа char записывается так: char *ch;Необходимо понимать, что ch — это не переменная типа char, а указатель на переменную данного типа, это совершенно разные вещи. Тип данных, на который указывает указатель (в данном случае это char), называется базовым типом указателя. Сам указатель является переменной, содержащей адрес объекта базового типа. Компилятор учтет размер указателя в архитектуре компьютера и выделит для него необходимое количество байтов, чтобы в указатель поместился адрес. Базовый тип указателя определяет тип объекта, хранящегося по этому адресу. В одном операторе объявления можно одновременно объявить и указатель, и переменную, не являющуюся указателем. Например, оператор int x, *y, count;объявляет х и count как переменные целого типа, а у — как указатель на переменную целого типа. В следующей программе операторы * и & используются для записи значения 10 в переменную target. Программа выведет значение 10 на экран. Поиск по сайту: |
Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Студалл.Орг (0.009 сек.) |