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;
    	};	
    

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

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

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

    Как правило для обрезания z-строки до нулевой длины применяют присваивание первому байту нуля.
    
    	char   str[64];
    	str[0] = 0;
    

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

    
    	char   str[64];
    	PTR32(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 unsigned char * без преобразования типов).
  • Для unicode-символов требуется отдельная функция wcslen
  • Последние 2 пункта можно было бы простить, но дополнительная проверка на ноль перед вызовом strlen нагружает и без того сложный код. Потому мы чаще всего пользуемся функцией sstrlen, определенной в slib.h.Эта функция определена для аргументов типа (const char *), (const unsigned char *) и (const wchar_t *).

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

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

    sstrcpy

    Несмотря на то, что очевидным решением для копирования одной z-строки в другую является стандартная функция strcpy, мы чаще всего применяем собственную реализации sstrcpy().Основная причина для такой подмены - небезопасность strcpy: эта функция не проверяет аргументы на 0 (в общем случае). Кроме того, в библиотеке SLIB присутствуют 3 переопределенных функции для параметров типа char *, unsigned char *, 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.

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