Шаг 207.
Язык программирования C#. Начала.
Указатели. Адресная арифметика
На этом шаге мы рассмотрим особенности выполнения такой арифметики.
На предыдущих шагах мы видели, что адрес - это целое число. Мы можем представлять себе одномерную последовательность ячеек (хотя в действительности все намного сложнее - но в данном случае
это не важно), каждая размером в 1 байт. У каждой ячейки есть адрес (целое число). Будем исходить из того, что адрес увеличивается справа налево. Адреса соседних ячеек отличаются на 1. Если
имеется некоторая ячейка с адресом, то соседняя ячейка справа имеет адрес на единицу меньше, а соседняя ячейка слева имеет адрес на единицу больше. Для записи значений может использоваться
несколько ячеек (будем называть это блоком) - все зависит от типа значения. Например, значение типа double занимает 8 ячеек, значение типа int занимает 4 ячейки, значение типа char
занимает 2 ячейки, а значение типа byte занимает 1 ячейку. То есть для записи значений разных типов используются блоки памяти разного размера. В указатель при этом записывается адрес первой
ячейки в блоке. Поэтому если в памяти подряд размещено два значения типа byte, то адреса блоков, в которые записаны эти значения, будут отличаться на 1. Если рядом в памяти два блока с
int-значениями, то адреса этих блоков отличаются на 4. Адреса соседних double-блоков отличаются на 8. Таким образом, дискретность изменения адресов соседних блоков памяти определяется типом значений, которые записаны в эти блоки.
Здесь приводятся объемы памяти, которые должны выделяться для значений разных типов по стандарту языка C#. Узнать фактический объем памяти (в байтах), выделяемый компилятором
под значение определенного типа, можно с помощью инструкции sizeof. Например, результатом выражения sizeof(int) является целое число, равное количеству байтов, выделяемых для значения
типа int (должно быть равно 4). Значение выражения sizeof(char) - объем памяти (в байтах), выделяемый для значения типа char (по стандарту должно быть 2).
Считывание значения из области памяти и запись в область памяти значения с использованием указателей - далеко не единственные поддерживаемые в C# операции. Есть группа важных и полезных операций, которые могут выполняться с указателями.
Далее под ячейкой будет подразумеваться однобайтовый блок, а под блоком в общем случае мы будем подразумевать группу из нескольких ячеек. Фраза "указатель ссылается на ячейку" понимается в
том смысле, что указатель содержит адрес соответствующей ячейки в качестве значения.
- Указатели можно сравнивать с помощью операторов ==, !=, <, >, <= и >=. При этом сравниваются числовые значения адресов.
- Можно вычислять разность указателей (одного типа). Результатом будет целое число, равное количеству блоков между ячейками, на которые ссылаются указатели. Другими словами, целое число определяет,
на сколько блоков памяти смещена одна ячейка по отношению к другой. Размер блока памяти (единица, в которой измеряется смещение) определяется типом базового значения, указанного при объявлении указателей.
Допустим, p и q являются указателями на значения типа int, а разность указателей q-p равна 3. Это означает, что если взять ячейку с адресом из указателя p и выполнить
смещение на 3 int-блока (в направлении увеличения адреса), то получим ячейку, адрес которой записан в указатель q. Поскольку один int-блок - это 4 однобайтовых блока, то разность q-p, равная 3,
означает, что ячейка, на которую ссылается указатель q, смещена по отношению к ячейке, на которую ссылается указатель р, на 12 однобайтовых блоков. Соответственно, разность адресов в указателях
q и р равна 12. Если бы указатели р и q были предназначены для работы с char-значениями, то речь бы шла о смещении на 3 блока, каждый из которых состоит из 2 ячеек (значение типа
char записывается в 2 однобайтовых блока). Поэтому адрес ячейки, на которую ссылается указатель q, был бы больше адреса из указателя р на 6. Отрицательная разность указателей означает, что смещение выполняется уменьшением адреса.
- К указателю можно прибавлять целое число, а также из указателя можно вычитать целое число. Результатом будет адрес ячейки, смещенной по отношению к адресу из исходного указателя на количество блоков, определяемых прибавляемым или вычитаемым числом.
Размер блока определяется типом, указанным при объявлении указателя. При прибавлении положительного числа адрес увеличивается, а при вычитании положительного числа адрес уменьшается.
Так, если р является указателем для int-значений, то результатом выражения р+3 является адрес ячейки, смещенной (с увеличением адреса) по отношению к ячейке с адресом из указателя
р на 3 int-блока (или 12 однобайтовых блоков). Результат выражения р-3 вычисляется аналогичным образом, но адрес при этом уменьшается.
- Указатели можно индексировать: после имени указателя в квадратных скобках указывается целочисленный индекс, который может быть в том числе и отрицательным. Результатом выражения
на основе проиндексированного указателя является значение, записанное в блоке, смещенном по отношению к ячейке, на которую ссылается указатель. Количество блоков для смещения определяется индексом.
Если индекс положительный - адрес при смещении увеличивается, если индекс отрицательный - адрес при смещении уменьшается. Через выражение на основе проиндексированного указателя можно прочитать
значение в блоке памяти и записать значение в этот блок памяти (присвоив выражению значение). Например, если p является указателем на int-значение, то выражение p[3] представляет собой
int-значение в блоке, смещенном на 3 int-блока по отношению к ячейке с адресом из указателя p.
Перечисленные выше правила имеют простые и очевидные последствия. Например, если указатели (одного типа) ссылаются на соседние блоки, то разность этих указателей равна 1 или -1,
в зависимости от того, как относительно друг друга расположены блоки. При этом разность адресов, записанных в указатели, отличается на значение, равное объему (в байтах) блока памяти, выделяемого
для записи значения данного типа. Далее, если p - некоторый указатель, то p[0] - значение, записанное в блоке, на который ссылается p (то есть то же, что и *p).
Значениями выражений p+1 и p-1 являются адреса блоков, соседних с блоком, на который ссылается указатель p. Если k - целое число, то выражение p[k] эквивалентно
выражению *(p+k), и так далее (таких интересных соотношений довольно много).
На следующем шаге мы продолжим изучение этого вопроса.
Предыдущий шаг
Содержание
Следующий шаг