Предисловие.
Без указателей не обойтись при разработке на языке Си, т.к. они слишком эффективны. Все как в реальной жизни, проще указать на информацию в книге, чем прочитать её лично. Также, затронем выделение и освобождение памяти, и структуры.
Зачем вам нужны указатели.
Я расскажу, почему вам нужно их использовать:
- Указатель почти ничего не весит (32 бита), что хорошо, учитывая что у сеги всего 64 Кб оперативной памяти.
- Указатель можно передать в функцию, это значит, что вы можете изменять переменную которую передали в функцию в качестве аргумента.
- Указывать можно не только на переменную, но и на функцию, что дает возможность делать эффективные state-машины (массив функций).
Разбираемся с указателями.
Указатель — это переменная, которая хранит не данные, а адрес в памяти. Поэтому и называется указатель, он указывает на данные.
Технически, указатель ничем не отличается от обычной переменной, различие наступает в способе получения данных из неё и записи в неё. Указатель весит 32 бита, поэтому используйте его лишь тогда, когда это выгодно, например для больших структур.
Для создания указателя применяется «*» (звездочка) после типа.
u32* pointerTest;
Для получения адреса переменной используется «&» (Амперсанд).
Давайте, передадим адрес переменной valueTest в указатель pointerTest, а потом, получим данные из указателя.
u32 valueTest = 1337; //Создали переменную valueTest и записали в неё значение
u32* pointerTest = &valueTest; //Передали адрес valueTest в указатель pointerTest
KLog_U1("PointerTest: ", *pointerTest); //Получили данные из указателя pointerTest, и вывели в качестве лога.
Заметьте, что у указателя тот же тип что и у обычной переменной.
u32 u32*
В результате получили значение из указателя.
Резюмируя. В pointerTest хранится адрес valueTest, мы вывели данные по адресу valueTest с помощью «*».
Чтобы получить данные по адресу который хранится в указателе, используйте «*», чтобы посмотреть адрес который хранится в указателе, не используем «*».
KLog_U1("PointerTest: ", pointerTest); //Адрес
KLog_U1("PointerTest2: ", *pointerTest); //Данные
Про вес указателей.
Данный текст написал werton
На m68k указатели 32х битные (как ни странно для 16 битного процессора) и занимает соответственно 4 байта в памяти, т.е. эквивалентен по размеру типу u32/s32/f32. Так что передавать по указателю переменные простых типов не имеет, практически, никакого смысла. Так же, не имеет особого смысла по указателю передавать структуры размер которых <= 4 байт, например вектора с полями на <= u16, или встроенные структуры типа BoxCollision, которая имеет 4 поля по u8.
Указатель на структуру.
Данный пример очень простой, и не показывает плюсов использования указателей. Поэтому, давайте усложним его, добавив структуру.
typedef struct {
u32 val1;
u32 val2;
u32 val3;
} testStruct;
testStruct val1 = (testStruct){1,2,3};
testStruct* pointerTest = &val1;
KLog_U1("PointerTest: ", pointerTest->val1);
Чтобы получить данные из структуры используется «.» (точка).
val1.val1
Тогда как, для получения данных из указателя на структуру, используется «->» (стрелка)
pointerTest->val1
В итоге, получили данные из структуры по указателю
Передаем массив в структуру.
Вот пример:
u32 testArr[3] = {11,22,33};
typedef struct {
u32* arrPointer;
} testStruct;
testStruct structVar;
structVar.arrPointer = testArr;
KLog_U1("PointerTest: ", structVar.arrPointer[2]);
Как видим, для передачи надо создать указатель с тем же типом что и массив
u32* arrPointer;
И присвоить массив указателю
structVar.arrPointer = testArr;
Структура которая указывает на себя.
Бывает так, что нужно сделать структуру которая может хранить указатель своего типа. Делается это так:
typedef struct {
struct EntityMerged* friend;
} EntityMerged;
В данном случае friend, это указатель на свою же структуру.
Передаем указатель в функцию.
Вы можете изменять переменную прямо из функции в которую вы эту переменную передаете. Делается это вот так.
void changeU32To5(u32* num){
*num = 5;
}
u32 testVar = 0;
changeU32To5(&testVar);
KLog_U1("testVar: ", testVar);
В функцию changeU32To5 передали адрес переменной testVar, и в этой функции сменили значение на 5.
Создаем массив функций.
Массив функций создается так:
void(* showEntityFuncArr[])(EntityMerged*) = {showEntitySimple, showBarierDead, showCoin};
Для функций такого типа:
void showEntitySimple(EntityMerged* entity) {
//something
}
Т.е. по следующему шаблону:
возвращаемый_тип(* название_массива_функций[])(тип_аргумента) = {название_функции1, название_функции2, ... , название_функцииN};
Если у функции нет аргументов, то вместо аргумента пишем void.
void(* showEntityFuncArr[])(void) = {showEntitySimple, showBarierDead, showCoin};
Чтобы выполнить функцию из массива функций, используем следующий шаблон:
название_массива_функций[индекс_в_массиве](аргумент);
И в качестве примера:
showEntityFuncArr[entity->entityType](entity);
Выделение памяти (аллокация).
Для выделения памяти (аллокации) используем MEM_alloc
MEM_alloc(количество_байтов)
На выходе функция выдает указатель на выделенный блок памяти, на память под нашу будущую переменную.
Если хотим выделить память под конкретную структуру, то используем sizeof.
curLocalVariables = MEM_alloc(sizeof(LocalVariableMerged)); //выделяем память под структуру LocalVariableMerged
Если нужно выделить память под массив, то умножаем sizeof(..) на количество элементов в массиве.
curLocalVariables = MEM_alloc(sizeof(LocalVariableMerged)*LocalVariableMerged_size);
Записывание данных в указатель.
Для записи в указатель используется функция memcpy у которой следующий синтаксис:
memcpy(указатель_куда, указатель_откуда, размер_в_байтах)
Т.е. в нашем примере с curLocalVariables, делаем так:
memcpy(curLocalVariables, LevelFull_arr[levelNum].variable_arr, sizeof(LocalVariableMerged));
где LevelFull_arr[levelNum].variable_arr это нужные данные текущего уровня.
Освобождение памяти (деаллокация).
Для освобождения памяти используется функция MEM_free, в которую передается указатель.
MEM_free(curLocalVariables);
После того как деаллоцируете все что нужно, используйте
MEM_pack();
Для уменьшения фрагментации памяти.
Узнать количество свободной RAM
Чтобы узнать сколько осталось RAM, используйте эту комманду:
KLog_U1("FreeMem: ", MEM_getFree());
Она возвращает количество свободных байтов, и выводит в лог эмулятора GENS KMod.