Petroglif

Базовые типы данных

Начнем обсуждение разработки с обзора используемых типов данных. В этой главе мы сосредоточимся на базовых или элементарных типах, которые применяются как самостоятельно, так и в составе более сложных структур.

Целочисленные типы

Наиболее простыми и популярными в использовании являются целочисленные типы данных.

Классификация

Целочисленные типы классифицируются по следующим двум критериям:

Длина
Измеряется в байтах или битах. Типичными вариантами могут быть следующие:
  • 1 байт
  • 2 байта
  • 4 байта
  • 8 байт
  • Возможны и другие случаи, но они скорее могут рассматриваться как экзотические либо применяемые в узкоспециализированных областях.
    Наличие знакового бита
    Здесь существует только две возможности - либо знаковый бит присутствует (это будет самый старший бит в двоичном представлении типа), либо он отсутствует. Беззнаковые (uint) целые типы имеют диапазон представления простирающийся от 0 до 2s-1 в то время как знаковые (signed) представляют числа от -(2s-1-1) до 2s-1-1.

    Какие целочисленные типы мы используем

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

    Мы здесь перечислим те типы которыми будем пользоваться и приведем особенности их применения:

    char
    Стандарт. Знаковое целое число размером 1 байт. Размер не гарантируется. Используется для представления символов в мультибайтовых строках.
    uchar
    Беззнаковое целое число размером 1 байт. Размер не гарантируется.
    wchar_t
    Условный стандарт (зависит от компилятора). Знаковое целое число размером 2 байта. Размер не гарантируется. Используется для представления символов в unicode-строках.
    short
    Стандарт. Знаковое целое число размером 2 байта. Размер не гарантируется.
    ushort
    Беззнаковое целое число размером 2 байта. Размер не гарантируется.
    int
    Стандарт. Знаковое целое число размером 4 байта. Размер не гарантируется. Наиболее часто используемый целочисленный тип. Главный недостаток - отсутствие гарантии размера. По этому для хранимых (persistent) структур применять не рекомендуется, вместо него в этих случаях следует использовать int32.
    uint
    Беззнаковое целое число размером 4 байта. Размер не гарантируется. Чаще всего используется в качестве целочисленного счетчика, поскольку в этом качестве процессором обрабатывается несколько оптимальнее, чем int.
    long
    Знаковое целое число размером 4 байта. Размер не гарантируется. Для 32-битных архитектур аналогичен int.
    ulong
    Беззнаковое целое число размером 4 байта. Размер не гарантируется.
    int8
    Знаковое целое число размером 1 байт. Размер гарантируется.
    uint8
    Беззнаковое целое число размером 1 байт. Размер гарантируется.
    int16
    Знаковое целое число размером 2 байта. Размер гарантируется.
    uint16
    Беззнаковое целое число размером 2 байта. Размер гарантируется.
    int32
    Знаковое целое число размером 4 байта. Размер гарантируется.
    uint32
    Беззнаковое целое число размером 4 байта. Размер гарантируется.
    int64
    Знаковое целое число размером 8 байт. Размер гарантируется.
    uint64
    Беззнаковое целое число размером 8 байт. Размер гарантируется.
    size_t
    Стандарт. Беззнаковое целое число, используемое для представления размера участка памяти.
    ssize_t
    Знаковое целое число, используемое для представления размера участка памяти. Часто применяется для представления результата вычитания одного адреса из другого (если уменьшаемое меньше вычитаемого, то результат будет отрицательным, что неприемлемо для стандартного типа size_t).

    Что касается канонизированных типов с суффиксом _t (int16_t, uint32_t и т.д.), то мы их не используем, однако технически их применение возможно без ущерба для правильной компиляции кодов.

    Общие подходы к выбору целочисленного типа в конкретных ситуациях

    При выборе целочисленного типа мы руководствуемся следующими соображениями:

  • В качестве счетчиков прямых (от меньшего к большему индексу) циклов следует применять uint.
    
    		for(uint i = 0; i < list.getCount(); i++) {
    			// ...
    		}
    	
  • Циклы с обратным отсчетом (от большего к меньшему), если это for для предотвращения переполнения при переходе через ноль лучше использовать int.
    
    		for(int i = list.getCount()-1; i >= 0; i--) {
    			// ...
    		}
    	
    Однако, вместо цикла for в таких случаях правильнее использовать if() do { } while. Например:
    
    		uint i = list.getCount();
    		if(i) do {
    			type & r_item = list.at(--i); // Обратите внимание на префиксный декремент
    		} while(i);
    	
  • В persistent-структурах правильнее использовать целочисленные типы с гарантированным размером. Например:
    
    		struct Entry { // @persistent
    			int32  Ident;
    			int16  Flags;
    			uint8  F2;
    			uint8  F3;
    		};
    	
  • Использования short, ushourt, int16, uint16 в большинстве случаев следует избегать. Они применяются в унаследованных интерфейсах либо в ситуациях, когда необходимо точно отмерить размер поля для структуры/класса.
  • Однобайтовые целочисленные типы (char, unsigned char, uchar, int8, uint8) применяются либо для представления символов в multibyte-строках, либо при очень жестких требованиях к плотности структуры данных, либо для ручного выравнивания структур данных (uint8). В последнем случае такие поля помечаются комментарием @alignment. Например:
    
    		struct Entry { // @persistent
    			int32  Ident;
    			int16  Flags;
    			uint8  Reserve[2]; // @alignment Выравнивает предыдущее поле по границе 4 байт.
    			double R;
    		};
    	
  • Для представления размера данных в локальных переменных почти всегда используется size_t. Но ни в коем случае не следует использовать size_t для представления элементов структур данных. Этот запрет связан с тем, что код может компилироваться как для 32-разрядной так и для 64-разрядной компьютерной архитектуры в результате чего размер структуры и смещение ее членов будет меняться.
    
    		void foo()
    		{
    			size_t data_size = sizeof(some_value); // Так правильно
    		}
    		
    		struct SomeStruc {
    			int  Ident;
    			size_t S; // ! Так нельзя. Используйте либо uint, uint32 либо uint64.
    			double R;
    		};
    	
  • Включенный файл stdint.h

    Основной каталог с include-файлами src/include содержит файл stdint.h. Это сделано для того, чтобы упростить сборку внешних компонентов, импортированных из мира open-source.

    Аспекты переносимости

    Здесь мы приведем несколько замечаний, касающихся переносимости данных между различными аппаратными платформами, операционными системами и языками программирования.

    Порядок следования байтов

    Разные процессорные архитектуры могут использовать разные соглашения о порядке следования байтов в двух- и четырех-байтовых целых числах.

    Существует два случая:

    little-endian
    big-endian

    Бинарная совместимость с другими языками программирования

    При необходимости обмена бинарными данными между приложениями написанных на разных языках программирования может оказаться существенным выбор типов данных.

    Типы для представления нецелых чисел

    Классификация

    Форматы данных для представления нецелых чисел будем классифицировать по следующим критериям:

    По признаку плавающей точки
    Дробное число можно представить либо с фиксированной десятичной точкой, либо с плавающей. В первом случае, формат хранения неявно предполагает позицию десятичной точки, представляя при этом число фактически в целочисленном виде. Все операции с такими числами учитывают положение десятичной точки. Для этого вида представления применяются либо, так называемые, двоично-десятичные форматы, либо целочисленные типы, с неявным определением положения точки. Во втором случае формат содержит информацию о положении десятичной точки. Такое представление описывается семейством стандартов IEEE 754.
    По длине
    Числа с плавающей точкой (IEEE 754) могут иметь длину 4 или 8 байт. Двоично-десятичные числа с фиксированной точкой могут иметь, вообще говоря, произвольную длину от 2 байт. Однако, в наших проектах мы чаще всего применяем 8-байтовые величины. Для чисел с фиксированной точкой в целочисленном формате мы обычно используем 4-байтовые целые (int32), с другой стороны, здесь так же нет ограничения для применения 1-, 2-, 8-байтовых целых типов (int8, int16, int64).

    Дата и время

    Дата

    В библиотеке SLIB определен специальный тип LDATE, содержащий бинарное представление даты и необходимые методы для работы с ней.Вот иллюстрация бинарной структуры типа LDATE:

    
    	uint16 Year;
    	uint8  Month;
    	uint8  Day;
    
    Таким образом, видно, что формат занимает 4 байта и использует структурированное представление даты. Альтернативным для структурированного, является представление, при котором дата хранится как количество дней, прошедших с какой-то фиксированной даты.

    Специальные значения даты
    Значение ZERODATE содержит нулевую (пустую) дату.

    Время

    В библиотеке SLIB определена специальная структура LTIME, содержащая бинарное представление даты и необходимые методы для работы с ней.Вот иллюстрация бинарной структуры типа LTIME:

    
    	uint8  Hour;
    	uint8  Minuts;
    	uint8  Seconds;
    	uint8  HandredthsOfSecond;
    
    Как и LDATE, данный тип занимает в памяти 4 байта.

    Специальные значения времени
    Значение ZEROTIME содержит нулевое время. Заметим, что нулевое время не является недопустимым. То есть, 00:00:00 - вполне допустимое значение.

    Дата-время

    Комбинированный тип LDATETIME (определен в SLIB.H) состоит из 8 байт. Старшие 4 байта заняты типом LDATE, младшие - типом LTIME.

    Унифицированное масштабированное представление даты/времени

    Относительно недавно в проект введен новый тип данных SUniTime позволяющих хранить масштабируемое время.

    Декларируется в файле slib.h.Бинарное представления этого типа состоит из 8 байт, самый старший из которых является индикатором, обеспечивающим информацию о масштабировании времени, установленном в остальных 7 байтах.

    Диапазон дат

    Тип DateRange используется для представления диапазона дат. Он состоит из даты нижней границы диапазона и даты верхней границы.

    
    	LDATE  low;
    	LDATE  upp;
    
    Существуют следующие особенности трактовки границ диапазона:

  • Если low равно ZERODATE, то нижняя граница не определена.
  • Если upp равно ZERODATE, то верхняя граница не определена.
  • Если upp не равно ZERODATE и low > upp, то период считается пустым.

    Класс DateRange содержит метод CheckAndSwap(), который меняет местами low и upp в таком случае.

  • Строковые типы

    SString

    Основной класс для представления multibyte-строк в системе. Декларируется в файле slib.h.

    SStringU

    Основной класс для представления unicode-строк. Декларируется в файле slib.h.

    Проблема кодировки multibyte-строк

    Унификации кодировки строк в проекте Papyrus - проблема. Из-за работы система со множеством источников данных и учитывая возраст проекта накопилось значительное число неразрешенных вопросов.

    Состояние вопроса следующее: базы данных используют кодировку cp866 (Windows OEM для русскоязычной версии операционной системы). Эта же кодировка трактуется как "INNER" то есть основная внутренняя. Далее, так как на текущий момент большая часть окружения для представления русских текстовых данных использует кодировку windows-1251 (Windows ANSI для русскоязычной версии операционной системы) то такая кодировка трактуется как "OUTER" то есть внешняя. Эта терминология применена для постепенной унификации кодировок первой целью которой является полный переход на OUTER-кодировку. Следующий этап предполагает полный переход либо на UTF-8 либо на UNICODE.Для трансляции кодировок для объектов SString используется функция SString::Transf() единственным аргументом которой задается направление перекодировки:

    CTRANSF_INNER_TO_OUTER 0x0102
    Из внутренней во внешнюю кодировку (OEM->ANSI)
    CTRANSF_OUTER_TO_INNER 0x0201
    Из внешней во внутреннюю кодировку (ANSI->OEM)
    CTRANSF_INNER_TO_UTF8 0x0103
    Из внутренней в utf-8
    CTRANSF_OUTER_TO_UTF8 0x0203
    Из внешней в utf-8
    CTRANSF_UTF8_TO_INNER 0x0301
    Из utf-8 во внутреннюю
    CTRANSF_UTF8_TO_OUTER 0x0302
    Из utf-8 во внешнюю

    Другие инструменты перекодировки

    Другие типы данных

    Несколько типов данных, которые часто используются в проекте:
    S_GUID_Base
    Базовое представление для GUID. Обеспечивает всю необходимую функциональность кроме конструктора и деструктора (ради возможности использовать как член union'ов).
    S_GUID
    Наследуется от S_GUID_Base и реализует конструктор и деструктор.
    SVerT
    SBaseBuffer
    STempBuffer
    SBuffer

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