Плавающие энумы в RayFoundation
Постановка задачиНеобходимо полностью абстрагировать перечисляемые типы, от их числового значения в С в целях обфускации кода. Т.е. после каждого обращение к плавающему энуму будет менятся таблица значений конкретных выражений. (В java такое было бы сделать на порядок проще, т.к. там enum является по сути классом, но далее речь будет идти о чистом С)Немного о самих энумах Перечисляемый тип (сокращённо перечисле́ние, англ. enumeration, enumerated type) — в программировании тип данных, чьё множество значений представляет собой ограниченный список идентификаторов.Т.е. по факту это набор данных типа словарь (хеш-таблица, NSDictionary, map) в котором ключ и значение представлены цифрами.Обычный энум в С представлен в коде:
typedef enum enumName { first, second, third […] } enumName; Сделан для удобства программистов, чтобы задать набор целочисленных констант некие first будет представлен как 0, second как 1, third как 3 и т.д.) т.е. чтобы во время написания программыне писать:
unsigned firstPlace = 0; unsigned secondPlace = 1; unsigned thirdPlace = 2; А писать более понятный код:
unsigned firstPlace = first; unsigned secondPlace = second; unsigned thirdPlace = third; Энумы можно почти полностью заменить директивой #define:
#define first 0 #define second 1 #define third 2 и предыдущий код будет выглядеть так же, только цвет слов first, second, third изменится (если конечно в ide установлена такая фича). Но синтасический сахар enum«ов в том, чтобы каждый раз не писать слово #define и в с++ компиляторе выкидывать warning«и о том, что дескать type-safety не соблюден, плюс автоинкремент значений с заданого. Т.е.
typedef enum enumName { first = 3, second, third […] } enumName; То second будет равен 4, third — 5. Плюс разные слова могут иметь одно и то же значение:
typedef enum codes { first_opcode = 1, second_opcode = 1, third_opcode, fourth_opcode } codes; Такой код не выдаст ошибку, при этом third_opcode будет равен 2, а fourth_opcode — 3.Плюс можно указать кол-во значений в перечислении с последовательным инкрементом:
typedef enum codes { first_opcode, second_opcode, third_opcode, fourth_opcode, opcode_count } codes; opcode_count = количесво значений в энуме + значение первого элементаПо дефолту значение первого элемента 0, и через автоинкремент мы просто получаем чистое кол-во значений в энуме.
Т.к. меня интересуют исключительно стандартные энумы, т.е. предназначенные именно для разделения множества словесных значений к числовым, я буду использовать именно их. Далее для понимания следующего синтаксиса маленькое разьяснение тут
Размышления Для того чтобы сделать плавающий энум необходимо всего две вещи: ГПСП (генератор псевдо-случайной последовательности) и контейнер для хранения целочисленных беззнаковых пар ключ-значение. Я использовал для этого ранее написанный словарь в RayFoundation (не буду здесь писать о нем, исходники доступны по адресу)Нам интересен только его интерфейс: class (RDictionary) //--------------------------------------------------------------------- discipleOf (RCompareDelegate) members RArray *keys; RArray *values; endOf (RDictionary) //---------------------------------------------------------------- constructor (RDictionary)); destructor (RDictionary); method (void, initDelegate, RDictionary), const RCompareDelegate *delegate); method (void, setObjectForKey, RDictionary), pointer value, pointer key); method (pointer, getObjectForKey, RDictionary), pointer key); В нем есть два само-удлиняющихся массива — это массив ключей и значений соответственно, делегат для определения равности ключей (хеш-функция в хеш-таблице) и методы setObjectForKey и getObjectForKeyКоторые задают значение по ключу и получают его.Таким образом, если обернуть стандартный enum в оболочку словаря, можно будет изменять значения конкретного члена перечисления в runtime (времени исполнения программы), ведь для стандартных enum«ов эта фича недоступна.
Решение Далее идет исхдный код реализации плавающего энума с коментариями (целиком доступен на гитхабе).Интерфейс:
class (RFloatingEnum) discipleOf (RDictionary) byte isChangesAfterCall; // если == 1 — перестаивает enum после каждого вызова compareValueToKey pointer (*nextElementForCode)(pointer code); // функция-делегат для генерации случайных значений (ГПСП) endOf (RFloatingEnum) constructor (RFloatingEnum), pointer (*)(pointer), RRange range); // диапазон энума — стартовое значение и количество элементов destructor (RFloatingEnum); printer (RFloatingEnum);
method (RCompareFlags, compareValueToKey, RFloatingEnum), pointer value, pointer key); method (void, changesAfterCall, RFloatingEnum), byte flag); method (void, rebase, RFloatingEnum)); // изменяет коды (значения) исходя из функции-делегата nextElementForCode #define RFloatingEnumName (enumName) concatenate (RFloatingEnum, enumName) #define createFloatingEnum (enumName, delegate, enumFirstValue, enumValuesCount) \ const RFloatingEnum *RFloatingEnumName (enumName) = $(NULL, c (RFloatingEnum)), delegate, makeRRange (enumFirstValue, enumValuesCount)) #define checkValueToKey (enumName, value, key) $(RFloatingEnumName (enumName), m (compareValueToKey, RFloatingEnum)), value, key) #define printEnum (enumName) $(RFloatingEnumName (enumName), p (RFloatingEnum))) #define rebaseEnum (enumName) $(RFloatingEnumName (enumName), m (rebase, RFloatingEnum))) #define deleteEnum (enumName) $(RFloatingEnumName (enumName), d (RFloatingEnum))); deallocator (RFloatingEnumName (enumName)) #define setFloatingEnum (enumName) $(RFloatingEnumName (enumName), m (changesAfterCall, RFloatingEnum)), 1) Реализация:
constructor (RFloatingEnum), pointer (*nextElementForCode)(pointer), RRange range) { pointer iterator; object = allocator (RFloatingEnum); if (object!= NULL) { master (object, RDictionary) = makeRDictionary (); if (master (object, RDictionary) != NULL) { object→classId = registerClassOnce (toString (RFloatingEnum)); object→nextElementForCode = nextElementForCode; object→isChangesAfterCall = 0; // начальная генерация значений fromStartForAll (iterator, range.from, range.count) { $(master (object, RDictionary), m (setObjectForKey, RDictionary)), object→nextElementForCode (iterator), iterator); } } } return object; } destructor (RFloatingEnum) { if (object!= NULL) { deleteRD (master (object, RDictionary)); } else { RPrintf («ERROR. RFE. Destruct null!\n»); } } printer (RFloatingEnum) { uint64_t iterator; RPrintf (»\n%s object %p: { \n», toString (RFloatingEnum), object); RPrintf (» Count: %qu \n», master (object, RDictionary)→keys→count); RPrintf (» Free: %qu \n», master (object, RDictionary)→keys→freePlaces); forAll (iterator, master (object, RDictionary)→keys→count) { RPrintf (»\t %qu — {», iterator); RPrintf (» %qu: %qu } \n», $(master (object, RDictionary)→keys, m (elementAtIndex, RArray)), iterator), $(master (object, RDictionary)→values, m (elementAtIndex, RArray)), iterator)); } RPrintf (»} end of %s object %p \n\n», toString (RFloatingEnum), object); } method (RCompareFlags, compareValueToKey, RFloatingEnum), pointer value, pointer key) { RCompareFlags flag; pointer obj = $(master (object, RDictionary), m (getObjectForKey, RDictionary)), key); if (obj == value) { flag = equals; } else { flag = not_equals; } if (object→isChangesAfterCall == 1) { $(object, m (rebase, RFloatingEnum))); } return flag; } method (void, changesAfterCall, RFloatingEnum), byte flag) { object→isChangesAfterCall = flag; } method (void, rebase, RFloatingEnum)) { pointer iterator; $(master (object, RDictionary)→values, m (flush, RArray))); // перестраиваем только массив значений forAll (iterator, master (object, RDictionary)→keys→count) { addObjectToRA (master (object, RDictionary)→values, object→nextElementForCode (iterator)); } } Пример использования и замечания typedef enum codes { first_opcode, second_opcode, third_opcode, fourth_opcode, opcode_count } codes;
int main (int argc, const char *argv[]) { // создаем плавающий енум на основе существующего стандартного энума createFloatingEnum (codes, rand, first_opcode, opcode_count); // т.к я не вызываю seed у меня 282475249 показывает Bingo if (checkValueToKey (codes, 282475249, second_opcode) == equals) { RPrintf («Bingo!\n»); } else { RPrintf («Not Bingo =(»); } // печатаем плавающий энум printEnum (codes); // вызываем перестройку вручную rebaseEnum (codes); printEnum (codes); // разрешает перестройку после обращения checkValue setFloatingEnum (codes); checkValueToKey (codes, 282475249, second_opcode); printEnum (codes); printEnum (codes); checkValueToKey (codes, 282475249, second_opcode); printEnum (codes); deleteEnum (codes); return 0; } Как мы видим каждый вызов printEnum (codes) показывает разные значения для ключей. Таким образом поставленная задача выполнена.
Замечания В стандартный энум должен быть дефолтным, т.е. не надо присваивать никаких значений в энум. И последним элементом должен быть елемент количества энума (тут впринципе как хотите, можете и вручную считать, я предпочитаю такой способ).И последнее, то что стандартный энум дефолтный не значит, что значения по ключам не будут повторятся (если вам нужно), все зависит от порождающей функции. В примере порождающей функцией был простой rand, но если делать серьезно, нужно проверять, чтобы значения не повторялись на больших энумах.Вкусности для тех, кто дочитал сюда: #define RFloatingEnumName (enumName) concatenate (RFloatingEnum, enumName) #define createFloatingEnum (enumName, delegate, enumFirstValue, enumValuesCount) \ const RFloatingEnum *RFloatingEnumName (enumName) = $(NULL, c (RFloatingEnum)), delegate, makeRRange (enumFirstValue, enumValuesCount)) #define checkValueToKey (enumName, value, key) $(RFloatingEnumName (enumName), m (compareValueToKey, RFloatingEnum)), value, key) #define printEnum (enumName) $(RFloatingEnumName (enumName), p (RFloatingEnum))) #define rebaseEnum (enumName) $(RFloatingEnumName (enumName), m (rebase, RFloatingEnum))) #define deleteEnum (enumName) $(RFloatingEnumName (enumName), d (RFloatingEnum))); deallocator (RFloatingEnumName (enumName)) #define setFloatingEnum (enumName) $(RFloatingEnumName (enumName), m (changesAfterCall, RFloatingEnum)), 1) Макрос RFloatingEnumName определяет далее используемое имя константной переменной для энума (состоящее из склеивания имени стандартного энума и слова RFloatingEnum). CreateFloatingEnum cоздает и инициализирует константную переменную (указатель) с таким именем. Остальные макросы просто неявно вызывают методы у этой константной переменной.Спасибо за внимание. Надеюсь кому-либо пригодится.
P.S. присылайте или пишите в коментах идеи по улучшению RayFoundation.