Автор rdc
От переводчика: статья была написана для устаревшей
версии freebasic.
В итоге статью и пример пришлось изменить, поскольку
арифметика указателей в старой версии
freebasic была
несколько иной.
Если вы читали статью
введение в указатели вы
знаете, что указатели содержат адреса расположения в памяти. Можно
манипулировать данными в этих местах памяти, используя оператор *.
Использование указателей с данными элемента не является проблемой, но что
делать, если необходимо хранить несколько элементов данных вместе и
манипулировать ими с помощью одного указателя? Это выглядит немного сложнее,
если вы не понимаете, каким образом данные хранятся в памяти.
Место памяти в компьютере — 1 байт длиной. Этого достаточно, чтобы
разместить один символ ANSI (в отличие от символов Юникода, которые имеют
два байта. Мы не будем обсуждать символы Юникода в этой статье.) Однако все
типы данных имеют разную длину в байтах. Вот простая программа, которая
отображает длину каждого типа данных в байтах.
Dim a As Byte
Dim b As Short
Dim c As Integer
Dim d As LongInt
Dim au As UByte
Dim bu As UShort
Dim cu As UInteger
Dim du As ULongInt
Dim e As Single
Dim f As Double
Dim g As Integer Ptr
Dim h As Byte Ptr
Dim s1 As String * 10 'строка
с фиксированной длиной
Dim s2 As String 'строка
с переменной длиной
Dim s3 As ZString Ptr 'zstring
s1 = "Hello World!"
s2 = "Hello World from FreeBasic!"
s3 = Allocate( Len( s2 ) + 1 )
*s3 = s2
Print "Byte: ";Len(a)
Print "Short: ";Len(b)
Print "Integer: ";Len(c)
Print "Longint: ";Len(d)
Print "UByte: ";Len(au)
Print "UShort: ";Len(bu)
Print "UInteger: ";Len(cu)
Print "ULongint: ";Len(du)
Print "Single: ";Len(e)
Print "Double: ";Len(f)
Print "Integer Pointer: ";Len(g)
Print "Byte Pointer: ";Len(h)
Print "Fixed String: ";Len(s1)
Print "Variable String: ";Len(s2)
Print "ZString: ";Len(*s3)
Deallocate s3
Sleep
Вывод:
Byte: 1
Short: 2
Integer: 4
LongInt: 8
UByte: 1
UShort: 2
UInteger: 4
ULongInt: 8
Single: 4
Double: 8
Integer Pointer: 4
Byte Pointer: 4
Fixed String: 10
Variable String: 27
ZString: 27
Обратите внимание, что длина указателя всегда равна 4 байтам (так же, как у
integer), независимо от данных, на которые этот
указатель указывает. Это потому, что он содержит адрес памяти , а не сами
данные.
Глядя на длину различных типов данных, вы можете увидеть, что если вам нужно
выделить (
Allocate)
достаточно места для 10
Integer, вам потребуется
40 байт памяти. Каждый
integer занимает 4 байта.
Поэтому вопрос стоит в том: как сделать доступ к каждому
integer из буфера памяти? Ответ прост: использовать арифметику
указателей. Взгляните на следующую программу.
Dim a As Integer
Dim aptr As Integer Ptr
'Выделение достаточного места для 2 переменных
integer
aptr = Allocate(Len(a) * 2)
'Загрузка нашего первого integer
*aptr = 1
Print "Int #1: ";*aptr
'Перемещение указателя на следующий integer
'aptr + 1
*(aptr + 1) = 2
Print "Int #2: ";*(aptr + 1)
Deallocate aptr
Sleep
End
В этой программе мы создаем две переменные,
Integer
и
Integer Pointer.
Aptr будет указывать на наш буфер памяти, который
будет содержать два
integer. Функция
Allocate
требует размер буфера, поэтому мы умножаем размер
integer
на 2, чтобы зарезервировать 8 байт памяти (каждый
integer займет 4 байта).
После процесса распределения,
Aptr содержит адрес
первого байта нашего буфера памяти. Для хранения первого числа все просто:
используем оператор * к
Aptr и значение 1. Чтобы
напечатать значение, мы снова просто используем оператор * к
Aptr.
Теперь вопрос: каким образом компилятор знает, что значение 1 должно быть
помещено в 4 байта и не в 1 или 2 байта? Это потому, что мы размер
Aptr указывается как
integer ptr.
Компилятор знает, что
Integer занимает 4 байта и
поэтому загружает данные в четыре байта памяти. Вот почему когда мы выводим
значение, мы получаем 1, а не какие либо странные числа.
Чтобы загрузить второе значение в наш буфер, мы используем:
*(aptr + 1) = 2
Это может выглядеть немного странно на первый взгляд.
Aptr указывает на первый байт в нашем буфере памяти.
Integer содержит 4 байта, поэтому чтобы попасть в
следующую позицию байта
Integer, мы должны
добавить 1 к
Aptr. Почему прибавляется 1 , а не
4 как в некоторых других языках
?
Потому что указатель показывает на
integer (Integer
ptr), то есть при прибавлении к адресу 1
, будет прибавляться
integer (4 байта
)
. Если бы у нас был указатель
Short ptr , то при
Aptr+1
прибавлялось бы 2 байта, а если бы у нас был указатель
Byte ptr
,
то при
Aptr+1
прибавлялся бы 1 байт.
Нам нужны скобки
вокруг операции добавления, потому что оператор * имеет более высокий
приоритет, чем +. Скобки гарантируют, что сначала выполнится операция
сложения, и только потом будет использоваться оператор *.
Обратите внимание, что мы отдельно не увеличиваем адрес, а берем косвенное
смещение относительно начала буфера. Делается это для того , чтобы когда
настанет время освободить память нашего буфера с помощью
Deallocate
, чтобы у нас не произошла ошибка доступа к памяти. Если возникает
необходимость непосредственно увеличивать указатель, то создайте временную
переменную-указатель, которой присвойте начальный адрес и эту переменную
можете увеличивать. В итоге наш начальный адрес буфера будет сохранен и мы
сможем его освободить когда потребуется или заново обратиться к начальному
адресу.
Буферы памяти и указатели являются мощным способом для хранения и управления
данными в памяти. Необходимо позаботиться о том, чтобы обращение к данным
было правильным в зависимости от типа данных, хранящегося в буфере.