Автор rdc
Указатель на тип данных уникален среди FreeBASIC числовых типов данных.
Вместо того, чтобы содержать данные, как и другие числовые типы, указатель
содержит адрес памяти данных.
На 32-битной системе, тип данных указателя составляет 4 байта. FreeBasic
использует указатели для ряда функций, таких как ImageCreate , а так же
указатели активно используются для внешних библиотек, таких как Windows API.
Работа с указателями довольно быстра, так как компилятор может напрямую
обращаться к ячейке памяти, на которую указатель указывает. Правильное
понимание указателей имеет важное значение для эффективного программирования
в FreeBASIC.
Для многих начинающих программистов, указатели могут показаться странными и
загадочными. Тем не менее, если вы держите в голове одно лишь правило, вы не
должны иметь никаких проблем с использованием указателей в вашей программе.
Правило очень простое: указатель содержит адрес, а не данные. Если вы
держите это простое правило в голове, вы не должны иметь никаких проблем с
использованием указателей.
Вы можете думать о памяти в своем компьютере как о ряде почтовых ящиков в
вашем местном почтовом отделении. Когда Вы войдете, чтобы арендовать
почтовый ящик, клерк даст вам номер ящика, например 100. Это - адрес вашего
почтового ящика. Вы решаете записать данный номер на листке бумаги и
помещаете его в ваш бумажник. На следующий день вы идете в почтовое
отделение и вытаскиваете листок бумаги. Вы определяете местонахождение ящика
с номером 100 и заглядываете в ваш ящик. Находите массу ненужной макулатуры.
Конечно, вы хотите выбросить макулатуру, но нет удобного мусорного ведра,
таким образом, вы решаете просто оставить почту в ящике, а выбросить ее
позже. Работа с указателями в FreeBasic очень подобна использованию
почтового ящика.
При объявлении указателя, он ни на что не указывает, то есть является
аналогом пустого листка бумаги. Чтобы использовать указатель, он должен быть
инициализирован к адресу памяти, который является нечто похожим на запись
числа 100 на листке бумаги. Если у вас есть адрес, то найти правильный ящик
не составляет проблем. Можно разыменовать указатель (по аналогии открыть
почтовый ящик) , для добавления или извлечения данных из области памяти. Как
вы можете видеть, есть три основные шага для использования указателей.
Продекларировать переменную указатель.
Инициализировать указатель на адрес памяти.
Разыменование указателя для работы с данными в памяти.
Это не сильно отличается от использования стандартных переменных, и вы
используете указатели во многом так же, как стандартные переменные.
Единственная реальная разница между ними заключается в том, что в
стандартной переменной вы имеете доступ к данным напрямую , а с указателем
необходимо разыменовать указатель для взаимодействия с данными.
FreeBasic имеет два типа указателей, типизированные и нетипизированные.
Типизированный указатель объявляется с типом данных.
Dim myPointer As Integer Ptr
Это указывает компилятору, что данный указатель будет использоваться для
целочисленных данных. Использование типизированных указателей позволяет
компилятору делать проверку указателя, чтобы убедиться, что вы не
используете неправильный тип данных с указателем.
Нетипизированные указатели декларируются с помощью ключевого слова
ANY.
Dim myPointer As Any Ptr
Нетипизированные указатели не имеют проверку типов и по умолчанию размер
байта. Нетипизированные указатели используются в C Runtime библиотеках и
многих других сторонних библиотеках, таких как API Win32, чтобы приспособить
тип указателя
void в С. Если вы не нуждаетесь в
нетипизированных указателях, вы должны использовать типизированные
указатели, чтобы компилятор мог проверять назначение указателя.
FreeBasic имеет следующие
операторы для
указателей.
Вы заметили, что оператор АДРЕС ИЗ (
addressof) не
только возвращает адрес памяти переменной, но он также может возвращать
адрес подпрограммы или функции. Вы можете использовать адрес подпрограммы
или функции, чтобы создать функцию обратного вызова, такую как ,
используемая в функции
CRT QSort..
FreeBasic также имеет ряд
функций памяти,
которые используются с указателями.
Эти функции могут использоваться для создания ряда динамических структур,
таких как связанные списки, динамические массивы и буферы, используемые со
сторонними библиотеками.
При использовании функции Allocate необходимо указать размер памяти в
зависимости от типа данных, используя уравнение number_of_elements * Sizeof(datatype).
Для распределения пространства 10
integer, ваш код
будет выглядеть следующим образом: myPointer = Allocate(10 * Sizeof(Integer)).
Integer имеет 4 байта , выделяя 10
integer, будет выделяться 40 байт памяти.
Выделяется не очищенный сегмент памяти, поэтому любые данные в сегменте
будут случайными, по своей сути это бессмысленные данные, пока указатель не
проинициализируется.
Callocate работает таким же образом, за исключением того, что данные обнуляются
(заполняются нулями). Чтобы выделить те же 10
integer,
используя Callocate, код будет выглядеть следующим образом: myPointer = Callocate(10, Sizeof(Integer)).
В отличие от Allocate, Callocate очистит сегмент памяти.
Reallocate изменит размер существующего сегмента памяти так, чтобы сделать
его больше или меньше по мере необходимости. Если новый сегмент больше, чем
существующий сегмент, то данные в существующем сегменте сохраняются. Если
новый сегмент меньше, чем существующий сегмент, данные в существующем
сегменте будут усечены. Reallocate не очищает добавленную память и не
изменяет существующие данные.
Все эти функции будут возвращать адрес памяти в случае успеха. Если функция
не может выделить сегмент памяти, то возвращается пустой указатель (0). Вы
должны проверить возвращаемое значение каждый раз при использовании этих
функций, чтобы убедиться, что сегмент памяти был успешно создан. Попытка
использовать пустой указатель приведет к нежелательному поведению или сбою
системы.
Нет внутреннего метода определения размера
выделения. Вы должны следить за этой информацией самостоятельно. |
Будьте осторожны, не используйте ту же
переменную указателя для повторного выделения сегментов памяти. Повторное
использование указателя без предварительного освобождения сегмента, на
который указатель указывает, приведет к потере сегмента памяти, в результате
чего произойдет утечка памяти. |
При создании сегмента памяти с помощью функций распределения памяти, вам
понадобится способ доступа к данным, содержащихся в сегменте. В FreeBASIC
есть два способа доступа к данным в сегменте; с помощью косвенного
арифметического оператора указателей и индексирования указателей.
Арифметика указателей, как следует из названия, складывает и вычитает
значения к указателю доступа к отдельным элементам в пределах сегмента
памяти. При создании типизированного указателя, например, Dim myPointer as Integer ptr,
компилятор знает, что данные используемые с этого указателя имеют размер
Integer или 4 байта. Указатель при инициализации, указывает на первый
элемент сегмента. Вы можете выразить это как *(myPtr + 0). Чтобы получить
доступ ко второму элементу, вам нужно добавить 1 к указателю, это может быть
выражено как *(myPtr + 1).
Так как компилятор знает, что он является указателем на
Integer, добавляя 1 к ссылке указателя, будет на самом деле
увеличивать адрес, содержащийся в myPtr на 4, размер
integer. Именно поэтому использование типизированных указателей
является предпочтительным по сравнению нетипизированными указателями.
Компилятор делает большую часть работы за вас в доступе к данным в сегменте
памяти.
Обратите внимание, что правильной является конструкция *(myPtr + 1) а не *myPtr + 1.
Оператор * имеет более высокий приоритет, чем +, так что *myPtr + 1 на самом
деле увеличит содержимое myPtr , а не адрес указателя.
myPtr , который возвращает содержимое ячейки памяти,
будет оцениваться в первую очередь, а затем +1 будет оцениваться, добавляя 1
к ячейке памяти. При упаковке myPtr + 1 в скобках, вы заставите компилятор
оценить сначала myPtr + 1 , это увеличит указатель адреса, а затем *
применяется для возврата содержимого нового адреса
Указатель индексации работает так же, как арифметика указателей, но детали
обрабатываются компилятором. *(myPtr + 1) эквивалентно myPtr[1]. Опять же,
поскольку компилятор знает, что myPtr является указателем на
integer, это дает возможность вычислить правильное
смещение памяти и вернуть правильное значение, используя индекс. Без разницы
какой формат вы используете, но индексный метод напоминает стандартный метод
доступа к массиву и его визуально легче понять, чем косвенный оператор.
FreeBASIC имеет набор функций для работы с указателями в дополнение к
операторам.
CPtr Преобразует выражение
к указателю data_type. Выражение может быть другим указателем или целым
числом
Peek Peek возвращает содержимое указателя ячейки
памяти. Data_type определяет тип ожидаемого данных
Poke Кладет значение выражения в ячейку памяти,
указанную указателем. Data_type определяет тип данных помещаемых в ячейку
памяти.
SAdd Возвращает место в памяти, где строковые
данные в динамической строке находится.
StrPtr Тоже что и Sadd.
ProcPtr Возвращает адрес функции. Это
работает так же, как оператор АДРЕС ИЗ @.
VarPtr Эта функция работает так же, как
оператор АДРЕС ИЗ @.
Функции
sAdd и StrPtr работают со строковыми
типами данных и возвращают адрес данных строки. Функции Peek и Poke были
добавлены для целей поддержки унаследованного кода. Procptr и VarPtr
работают так же, как оператор @, но Proptr работает только с процедурами и
функциями и VarPtr работает только с переменными. CPTR полезно для
преобразования нетипизированных указателей к типизированному указателю или
значению, например может пригодится при возврате значения из библиотеки
сторонних производителей.
Процедуры и функции, как переменные, находятся в памяти и имеют адрес,
связанный с их точкой входа. Вы можете использовать эти адреса для создания
события в своих программах, для создания псевдо-объектов и использовать в
функциях обратного вызова. Вы создаете указатель на функцию или процедуру
так же, как любой другой указатель, но объявляя переменную как указатель на
процедуру или функцию, а не как указатель на тип данных.
Перед использованием указателя на функцию, он должен быть инициализирован в
адрес подпрограммы или функции с помощью Procptr or @. После инициализации,
можно использовать указатель таким же образом, как вызов исходной процедуры
или функции.
Вы объявляете указатель на функцию с помощью синтаксиса анонимной
декларации.
Dim FuncPtr As Function(x As Integer, y As Integer) As Integer
Затем необходимо связать этот указатель на функцию с фактической процедурой
или функцией в коде.
Function Power(number As Integer, pwr As Integer) As Integer
Return number^pwr
End Function
FuncPtr = @Power
Затем можно вызвать указатель на функцию так же, как вы могли бы вызвать
реальную функцию.
FuncPtr(2, 4)
Хотя это не может быть полезным на первый взгляд, вы можете использовать эту
технику для реализации полиморфных функций, где один экземпляр переменной
может указывать на один из нескольких различных подпрограмм или функций.
Например, предположим, у вас есть объекты
DOG и
CAT. Обоим объектам нужен метод
SPEAK. Определяя
SPEAK как указатель на
функцию и привязывая его с процедурой Bark для собаки и процедурой Meow для
кошки, вы заставите произносить "ГАВ" для
DOG и
"МЯУ" для
CAT, в зависимости от типа объекта.
Одним из основных применений для указателей на функции, является создание
функций обратного вызова (
callback). Функция
обратного вызова является функцией, которую вы создаете в вашей программе, и
которая вызывается другой функцией или процедурой в вашем собственном
пространстве кода или во внешней библиотеке.
Windows
использует функции обратного вызова для перечисления объектов Window , такие
как шрифты, принтеры и формы...
Функция QSort, содержащаяся в Runtime Library C
, сортирует
элементы массива с помощью функции обратного вызова, чтобы определить
порядок сортировки. Прототип для функции QSort содержится в stdlib.bi:
Declare Sub qsort cdecl Alias "qsort" (ByVal As Any Ptr, ByVal As size_t, ByVal As size_t, ByVal As Function cdecl(ByVal As Any Ptr, ByVal As Any Ptr) As Integer)
Ниже перечислены информационные параметры для функции QSort.
Первый параметр это адрес на первый элемент массива.
Самый простой способ, чтобы передать эту информацию для QSort , является
добавление адреса оператора первого индекса элемента: @myArray(0).
Второй параметр это количество элементов в массиве, то есть значение
счетчика массива.
Третьим параметром является размер каждого элемента в байтах. Для массива
Integer, размер элемента будет 4 байта.
Четвертый параметр является указателем на пользовательскую функцию
сравнения. Функция должна быть объявлена с помощью спецификатора CDECL,
как показано в этом параметре.
Используя эту информацию, можно увидеть, как QSort работает. Передавая
адрес первого элемента вместе с подсчетом элементов, и размером каждого
элемента, QSort может проводить итерацию массива с использованием арифметики
указателей.
QSort возьмет два элемента массива, передаст их на вашу пользовательскую
функцию сравнения и использует возвращаемое значение функции сравнения для
сортировки элементов массива. Она делает это несколько раз, пока каждый
элемент массива не окажется в определенном порядке.
Вы должны объявить прототип функции со спецификатором CDECL , который
гарантирует, что параметры передаются в правильном порядке.
Declare Function QCompare cdecl (ByVal e1 As Any Ptr, ByVal e2 As Any Ptr) As Integer
Затем вы должны определить функцию.
'Функция QSort ожидает три числа
'от функции сравнения:
'-1: если e1 меньше e2
'0: если e1 равно e2
'1: если e1 больше e2
Function QCompare cdecl (ByVal e1 As Any Ptr, _
ByVal e2 As Any Ptr) As Integer
Dim As Integer el1, el2
Static cnt As Integer
'Получить количество вызовов и переданных элементов
cnt += 1
'Получая значения, необходимо привести в integer ptr
el1 = *(CPtr(Integer Ptr, e1))
el2 = *(CPtr(Integer Ptr, e2))
Print "Qsort called";cnt;" time(s) with";el1;" and";el2;"."
'Сравнение значений
If el1 < el2 Then
Return -1
ElseIf el1 > el2 Then
Return 1
Else
Return 0
End If
End Function
Затем вы должны вызвать функцию QSort передавая адрес функции обратного
вызова.
qsort @myArray(0), 10, SizeOf(Integer), @QCompare
В FreeBasic можно создать указатель на любой из поддерживаемых типов данных,
включая тип, данных указателя. Указатель на указатель полезен в ситуациях,
где вам нужно возвращать указатель на функцию или в создании
специализированных данных структур, такие как связанные списки и рваные
массивы. Указатель на указатель называется многоуровневым косвенным
обращением.
Одно из применений указателя на указатель является создание сегмента памяти,
который ведет себя так же, как массив. Например предположим, что вы хотите
создать сегмент памяти для хранения неизвестного количества
Integer. Вы можете создать сегмент динамической
памяти, который можно изменить во время выполнения и обработать как можно
больше необходимых
integer. Нужно начать с
создания указатель на указатель переменной.
Dim myMemArray As Integer Ptr Ptr
Затем вы инициализируете ссылку на указатель с помощью Allocate или Callocate.
'Создание 10 строк указателей integer
myMemArray = CAllocate(10, SizeOf(Integer Ptr))
Обратите внимание, что переменная инициализируется как
Integer Ptr ,
так как этот список будет указывать на другой список; это указатель,
указывающий на другой указатель. Затем можно инициализировать ссылку
указателя, созданием сегмента необходимой памяти.
'Добавить столбцы 10 integer в
каждой строке
For i = 0 To 9
myMemArray[i] = CAllocate(10, SizeOf(Integer))
Next
В этом фрагменте кода инициализируются отдельные указатели в списке 10
сегментов памяти, которые будут содержать фактические данные
integer.
'Добавить некоторые данные в сегмент памяти
For i = 0 To 9
For j = 0 To 9
myMemArray[i][j] = Int(Rnd * 10)
Next
Next
Этот фрагмент кода использует метод индексации для загрузки фактических
данных в сегменты памяти. Обратите внимание, что это выглядит и действует
как двумерный массив. Это может показаться полезным, ведь можно использовать
этот код хотя бы для создания динамического массива в определении типа. Так
как вы не можете (примечание: в новых версиях
freebasic
уже можете) иметь стандартный динамический массив в пределах типа,
данный метод позволяет получить эту функциональность.
Одна вещь, которую вам нужно знать, это как освобождать выделенную память.
Правило заключается в том, чтобы просто освобождать память в обратной
последовательности. Поскольку последними выделяются операции инициализации
данных сегментов памяти, вы должны освободить сначала эти сегменты памяти, а
потом вы можете освободить базовый указатель.
'Осовбождение сегмента памяти
For i = 0 To 9
Deallocate myMemArray[i]
Next
'Освобождение указателя на указатель
Deallocate myMemArray
Вы должны быть уверены, что вы освобождаете память в правильном порядке. В
противном случае вы будете в конечном итоге не освобожденные и недоступные
сегменты памяти. Эти утечки памяти могут вызвать целый ряд проблем в вашей
программе.