Petroglif

Общие функции

В этой главе обсуждаются общеупотребимые функции и методы, а именно:
  • Распределение памяти
  • Манипуляции с участками памяти (инициализация, копирование, перемещение)
  • Ввод-вывод
  • Манипуляции со строковыми переменными и константами
  • Математические функции
  • Распределение памяти

    Функции malloc, calloc, realloc, free, определенные в стандартной библиотеке не используются в проекте на прямую. Вместо них применяются статические функции класса SAlloc. Ниже приведено упрощенное определение этого класса.

    
    	class SAlloc {
    	public:
    		//
    		// Descr: Функция, замещающая malloc.
    		//
    		static void * FASTCALL M(size_t sz);
    		//
    		// Descr: Функция, замещающая calloc.
    		//
    		static void * FASTCALL C(size_t n, size_t sz);
    		//
    		// Descr: Функция, замещающая realloc.
    		//
    		static void * FASTCALL R(void * ptr, size_t sz);
    		//
    		// Descr: Функция, замещающая free.
    		//
    		static void   FASTCALL F(void * ptr);
    	};
    
    Существуют две причины, по которым реализовано такое замещение:
    1. Благодаря соглашению вызова FASTCALL код, использующий эти функции (а это очень значительное количество вызовов) становится компактнее. Отметим, что выигрыша в скорости вызова нет (чуть-чуть медленнее за счет избыточной вложенности).
    2. Появляется возможность (увы, пока не осуществленная) централизованно собирать статистику об обращениях к аллокатору памяти с целью реализации высокооптимизированного аллокатора.

    Проверка на null освобождаемой памяти

    Специальное замечание относительно освобождения памяти функциями free(), SAlloc::F() и оператором delete: стандарты c/c++ гарантируют, что эти функции игнорируют нулевые указатели. Поэтому не следует проверять на null указатели перед вызовом этих и родственных функций - не надо загромождать и без того гигантский код.Единственное исключение из вышеприведенного правила: если вы абсолютно уверены, что участок кода, где осуществляется освобождение распределенной памяти, критичен по быстродействию (очень часто вызывается, например, в циклах и т.д.) то проверка на null перед вызовом функции (оператора) освобождения памяти поможет сэкономить “пару тактов” процессора. Пометьте такую проверку комментарием // @speedcritical чтобы как-то оправдаться.

    Манипуляции с участками памяти

    Функции memcpy, memmove, memset, memcmp опеределены в slib.h как макросы, для использования реализации из библиотеки Agner Fog (www.agner.org).

    Для инициализации памяти нулевыми байтами вместо общеупотребимого memset(ptr, 0, size) используется собственная функция memzero(ptr, size). Мотивацией для этого является удаление лишнего параметра в огромном числе вызовов, что в купе с соглашением вызова FASTCALL дает заметное снижение размера кода. Кроме того, функция memzero отдельно обрабатывает некоторые размеры инициализируемых участков памяти для ускорения.Для обнуления участков памяти, размер которых может быть вычислен на этапе компиляции оператором sizeof используется макрос MEMSZERO(). Пример:

    
    	void foo(void * ptr, size_t size)
    	{
    		char local_buf[256];
    		MEMSZERO(local_buf); // Правильно: sizeof(local_buf) возвращает адекватный результат
    		// MEMSZERO(ptr); // ! Ни в коем случае - будут обнулены 
    			// только первые 4 байта (8 на x64)
    		memzero(ptr, size); // Правильно
    	}
    

    Еще один макрос THISZERO() используется для полного обнуления экземпляра структуры или класса. Часто используется в конструкторах.Осторожно: этот макрос ни в коем случае не следует использовать для классов, имеющих виртуальные функции - он обнулит указатель на таблицу виртуальных функций в результате чего программа обязательно аварийно завершится при попытке обращения к любой из виртуальных функций класса.

    
    	class SomeClass {
    	public:
    		SomeClass()
    		{
    			THISZERO(); // Здесь можно использовать. 
    				// Класс не определяет ни одной виртуальной функции.
    		}
    		int    A;
    		int    B;
    	};
    	
    	class SomeClassWithVirtualFunctions {
    	public:
    		SomeClass()
    		{
    			THISZERO(); // ! Ни за что не делайте так - виртуальный 
    				// деструктор или иные виртуальные функции заставляют 
    				// компилятор вставить первым членом в экземпляре класса
    				// указатель на таблицу виртуальных функций. 
    				// В результате обнуления этот указатель станет нулевым.
    		}
    		virtual  SomeClass()
    		{
    		}
    		int    A;
    		int    B;
    	};	
    

    Функция smemchr

    
    	const void * smemchr(const void * pHaystack, int n, size_t len);
    
    Функция smemchr следует использовать вместо memchr. Единственным обоснованием этого является то, что smemchr значительно быстрее нежели memchr.

    Тестирование и бенчмаркинг функции реализованы в тестовом кейсе SLTEST_R(memchr).

    Функции для работы со строками

    В этой секции мы обсудим ряд часто используемых функций для манипуляции с z-строками (то есть, классическими строками, состоящими из цепочки символов, завершающихся нулевым байтом).

    Обнуление строки

    Как правило для обрезания z-строки до нулевой длины применяют присваивание первому байту нуля.

    
    	char   str[64];
    	str[0] = 0;
    

    Определение длины строки

    Выяснение не является ли строка пустой

    При определении не является ли указатель на строку, переданный в качестве аргумента функции, пустой строкой следует использовать функцию isempty() вместо простого сравнения первого байта с нулем. Это намного безопаснее из-за того, что ликвидирует проблему потенциального обращения к нулевому указателю, который может быть использован как параметр при вызове функции.

    
    isempty(const char * pStr);
    
    Например:

    
       void foo(const char * pStr, int a)
       {
           if(!isempty(pStr)) {
    	       // do something with pStr;
    	   }
       }
    

    sstrlen и sstrleni

    Стандартная функция strlen почти идеальна в плане быстродействия, но имеет небольшие недостатки:

  • При передаче ей нулевого указателя вызывает системное исключение (не проверяет аргумент на ноль).
  • При компиляции c++ принимает только параметр типа const char * (но отказывается работать с const uchar * без преобразования типов).
  • Для unicode-символов требуется отдельная функция wcslen
  • Последние 2 пункта можно было бы простить, но дополнительная проверка на ноль перед вызовом strlen нагружает и без того сложный код. Потому мы чаще всего пользуемся функцией sstrlen, определенной в slib.h.

    Эта функция определена для аргументов типа (const char *), (const uchar *) и (const wchar_t *).Дополнительно в slib.h определены аналогичные 3 функции с именем strleni, возвращающие int, а не size_t. Это сделано из-за того что во многих случаях требуется получить именно знаковое значение (это - просто трюк, реализованный для того, чтоб обойтись без лишних преобразований типов).

    Копирование строк

    sstrcpy

    Несмотря на то, что очевидным решением для копирования одной z-строки в другую является стандартная функция strcpy, мы чаще всего применяем собственную реализации sstrcpy().

    Основная причина для такой подмены - небезопасность strcpy: эта функция не проверяет аргументы на 0 (в общем случае). Кроме того, в библиотеке SLIB присутствуют 3 переопределенных функции для параметров типа char *, uchar *, wchar *.Вместе с вышесказанным следует отметить, что sstrcpy сильно уступает стандартной strcpy по быстродействию из-за того, что компилятор превосходно оптимизирует вызовы strcpy.

    strnzcpy

    
    	char * FASTCALL strnzcpy(char * pDest, const char * pSrc, size_t maxlen);
    	char * FASTCALL strnzcpy(char * pDest, const uchar * pSrc, size_t maxlen);
    	char * FASTCALL strnzcpy(char * pDest, const SString & rSrc, size_t maxlen);
    	wchar_t * FASTCALL strnzcpy(wchar_t * pDest, const wchar_t * pSrc, size_t maxlen);	
    

    Копирование строки в буфер с ограниченной длиной. Функция strncpy из стандартной библиотеки не используется из-за странной особенности: если исходная длина превышает размер буфера назначения, то завершающий ноль в буфере установлен не будет.У функций семейства strnzcpy есть и собственная несколько необычная особенность: если параметр maxlen равен нулю, то функции считают буфер назначения (pDest) бесконечным (то есть, работают как sstrcpy).

    Вариант strnzcpy(char *, const SString &, size_t) реализован ради унификации в случае, если источником данных является экземпляр основного высокоуровневого класса представления строк SString.

    Точное сравнение строк

    
    	bool   FASTCALL sstreq(const char * pS1, const char * pS2);
    	bool   FASTCALL sstreq(const uchar * pS1, const uchar * pS2);
    	bool   FASTCALL sstreq(const uchar * pS1, const char * pS2);
    	bool   FASTCALL sstreq(const wchar_t * pS1, const wchar_t * pS2);	
    	bool   STDCALL  sstrneq(const char * pS1, const char * pS2, uint len);
    	bool   STDCALL  sstrneq(const uchar * pS1, const uchar * pS2, uint len);
    
    Функции с сигнатурой sstreq сравнивают две строки на равенство. Они используются для замены часто применяемой конструкции, в которой результат вызова strcmp сравнивается с нулем.

    То есть:

    
    	// Вместо
    	if(strcmp(s1, s2) == 0) {
    	}	// Следует использовать 
    	if(sstreq(s1, s2)) {
    	}
    

    Кроме сокращения кода функции sstreq применимы к аргументам типа const unsigned char *, const wchar_t * и к смешанной паре (const char *, const unsigned char *).Функции с сигнатурой sstrneq применяются в случае, если необходимо проверить равенство префиксов двух строк заданной длины.

    Cравнение строк без учета регистра символов

    
    	bool   FASTCALL sstreqi_ascii(const char * pS1, const char * pS2);
    	bool   FASTCALL sstreqi_ascii(const uchar * pS1, const uchar * pS2);
    	bool   FASTCALL sstreqi_ascii(const wchar_t * pS1, const wchar_t * pS2);
    	bool   FASTCALL sstreqi_ascii(const wchar_t * pS1, const char * pS2);	
    	bool   STDCALL  sstreqni_ascii(const char * pS1, const char * pS2, size_t maxlen);	
    

    Функции с сигнатурой sstreqi_ascii применяются для проверки эквивалентности двух строк с игнорированием разницы в регистрах символов, но только для ascii-символов (до 0x7f включительно).Самый частый паттерн использования этих функций - сравнение некоторой переменной строки с константным строковым литералом. Поскольку по коду обычно видно что литерал ограничен только ascii-символами.

    Например:

    
    	if(sstreqi_ascii(s, "Some string with a Mixed Case")) {
    	}
    

    Битовые операции

    Примитивные битовые операции (кроме очевидных |, &, ^, leading zeroes. Подсчитывает число старших нулевых бит до первого встреченного единичного бита. Если аргумент нулевой, то возвращает общее количество бит в аргументе.

    Примитив Ctz

    Count trailing zeroes. Подсчитывает число младших нулевых бит до первого встреченного единичного бита. Если аргумент нулевой, то возвращает общее количество бит в аргументе.

    Примитив Cpop

    Count population. Этот примитив чаще всего в различных программах и библиотеках называют popcount. Мы используем символ Cpop для того, чтобы он был в том же стиле, что и Clz и Ctz (C означает count).Подсчитывает общее число установленных бит в аргументе.

    Примитив Rotr

    Rotate right. Циклически перемещает все биты аргумента вправо (от старших - к младшим) на заданное вторым аргументом количество шагов. 'Циклически' означает, что биты, которые уходят за правый край значения возвращаются с левого его края. То есть, в отличии от обычного сдвига, циклический сдвиг обратимый.

    Примитив Rotl

    Rotate left.

    OOO "Петроглиф"
    Copyright © 2019