Showing posts with label ptr. Show all posts
Showing posts with label ptr. Show all posts

19/02/2020

Интересно о размерах (2): модели данных

В продолжение рассуждений-исследований о соотношениях разрядности машины и размерностей типов (Интересно о размерах (1): типы и разрядные сетки машин), рассмотрим, как это выглядит формально (научно), как эти соотношения формализованы в стандартах — внесём окончательную ясность в одном из основополагающих аспектах вычислительной техники.

Соотношение разрядности машины и разрядностей типов данных (на самом деле просто отношения разрядностей типов друг к другу) называется моделью данных. И на самом деле модель данных задаётся компилятором, а не разрядностью машины, ровно, как и не операционной системой. То есть, даже на одной ОС, используя разные компиляторы, можно получить разные соотношения (которые мы косвенно рассмотрели в предыдущей статье на примере программы). Моделей данных существует много, но обычно в распространённых связках разрядность машины/ОС/компилятор используется ограниченное количество.

Принятый (сокращённый) формат названия модели данных состоит из перечисления типов, которые относятся к максимальной разрядности и значения самой разрядности.

Типы это (в порядке возрастания): S  короткое целое число (short), I  целое число (int), L  длинное целое число (long), LL  двойное длинное целое число (long long), P  указатель (void *). Разрядность указывается в битах  32, 64, 128.

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

Пример, макрос которого мы использовали в предыдущей статье LP64, означает что компилятором задана следующая модель данных — 64-битными являются типы начиная с L (long), а именно — long и pointer. Указание LL излишне, так как он стоит рядом с указателем, и в таком случае считается равным ему. Если бы он был больше чем указатель, в конце было бы дописано LL128 (LP64LL128). Реальный пример ILP32LL64 (int, long и pointer — 32-битные, long long — 64-битный), эта модель использовалась в Win32 и некоторых UNIX'ах в 90-ые года. Можно задаться вопросом — как машина, разрядностью менее разрядности максимального типа вообще может оперировать такими типами? Верно — на операции с этими типами уходит более одной инструкции (такта), что крайне неэффективно с точки зрения вычислительных мощностей. 

Все типы, определённые до типов, указанных в названии модели считаются 32-битными, за исключением типа short — пока он, начиная с PDP-11 является 16-битным всегда. Это подразумевается, хотя возможна и полная запись: I32LP64 (и даже S16L32P64— эта модель данных и принята в стандартах Open System (UNIX является ОС относящейся к этому стандарту). В конце 90-х годов эта модель была согласована как основная для UNIX-систем совместным решением компаний, в том числе DEC, IBM, Sun Microsystems, HP, Intel, NCR.

Есть и отличный от предыдущих пример. В системах UNICOS (UNIX-подобная ОС для линейки суперкомпьютеров Cray) все типы являются 64-битными, и модель данных на этих машинах используется SILP64. И здесь используется символ S, так как даже short здесь 64-битный.

Ну и чтобы наше описание было полным, укажем какая модель данных была в Win16 и Win64. В Win16 использовалась LP32, что означает — 32-битные long и ponter, а int был 16-битным. А в Win64 используется LLP64, то есть, все типы до long long оставлены 32-битными. Сделано это из-за проблем с совместимостью и переписыванием WinAPI при переходе на 64-разрядные системы.

Для большинства UNIX-разработчиков ещё знакомы 32-разрядные системы, там принята модель данных ILP32, что означает что все типы, кроме short, являются 32-битными. И, собственно, единственным отличием 32-разрядных машин (компиляторов) от 64-битных является то, что pointer и long стал 64-битным. Что я и рассмотрел и доказал как один из методов определения разрядности машины в предыдущей статье. Все остальные типы остаются таких же размерностей, что и на 32-битных системах. Сделано это для совместимости кода. Но здесь стоит быть осторожным. В некоторых 32-битных программах типы int, long и pointer используются как синонимы, полагается, что их разрядности равны. Но так как размеры long и указателя в модели данных LP64 стали 64-битными мы должны помнить, что это изменение может вызвать много проблем при переносе кода с 32-разрядной платформы на 64-битную.
Поиск оптимальной модели данных, при каждом переходе на более высокую разрядность системы, является основополагающим моментом в обеспечении качественного процесса разработки ПО. Можно сказать, что модель данных является инструментом разработчика. Так как влиять мы на неё (модель) не можем, наша задача хорошо в них разбираться.

На этом пока всё.

22/10/2019

Интересно о размерах (1): типы и разрядные сетки машин

Короткая заметка о том, как соотносятся размеры целочисленных типов с разрядностью машины (процессора, контроллера).

Лет 10-15 назад я почему-то думал, что размер int'a (sizeof (int)) равен разрядности машины. Не знаю откуда я это взял, может, я как-то по-своему интерпретировал слова Б.Кернигана и Д.Ритчи:

«int  целое число, обычно имеющее типовой размер для целых чисел в данной системе»

Но выглядела эта идея довольно красиво  представьте себе, что у вас int всегда имеет размер разрядности машины: 16 — для 286, 32 — для 386 и после, 64  для Itanium (или кто там был первым среди x86-64?). В последствии, на практике, я увидел, что это не так.
Недавно (неделю-две назад) я решил окончательно разобраться с этим вопросом. Опишу это здесь.

Мои изыскания привели к следующим результатам:
1. Размер int'а никак не связан с разрядностью машины. Стандарт языка C описывает только одно правило соотношения между базовыми типами:

sizeof (short) <= sizeof (int) <= sizeof (long)

по-русски гласящее, что тип short будет либо меньше, либо равен int, который, в свою очередь, либо меньше, либо равен long. На практике, мы видим, что чаще всего размер int'а равен 4 байтам. Но размеры этих типов не определяются стандартом.

2. Да, функцией sizeof () можно определить разрядность машины  только не через размер типа, а через размер указателя на тип (подойдёт любой тип). Именно размер указателя равен разрядности машины, так как разрядность  это не только количество бит, с которыми машина работает за один такт и/или размер инструкции, но и размер поддерживаемого объёма памяти, для адресации которого машина выделяет и оперирует типами соответствующего размера. То есть, чтобы узнать разрядность машины можно воспользоваться sizeof([любой тип]*), например sizeof(int*).

Так как функция sizeof () реализована так, что размерности типов (и прочих констант) подставляются в момент компиляции (compile-time), по сути дела информативность её достаточно мала в силу того, что разрядность процессора задаётся (и может быть ограничена) компилятором и/или операционной системой и результат выполнения её после компиляции будет одинаков при всех последующих запусках. Для определения разрядности машины есть макрос __LP64__ и __LP32__ соответственно (эти макросы означают модель памяти, о 64-ёх разрядной можно почитать здесь 64 bit data models). Макрос использованный в коде, на этапе компиляции развернётся и не будет занимать процессорного времени на этапе выполнения, при этом выполнит задачу sizeof (указатель на тип).

Завершим описанное демонстрацией простенькой программой:

#include "stdio.h"
int main ()
{
  printf ("Compiled on ");
#if defined(__LP64__)
  printf ("64-bit platform\n");
#endif
#if defined (__LP32__)
  printf ("32-bit platform\n");
#endif
  printf ("sizeof (char)\t= %d bytes\n", sizeof(char));
  printf ("sizeof (short)\t= %d bytes\n", sizeof(short));
  printf ("sizeof (int)\t= %d bytes\n", sizeof(int));
  printf ("sizeof (long)\t= %d bytes\n", sizeof(long));
  printf ("sizeof (float)\t= %d bytes\n", sizeof(float));
  printf ("sizeof (double)\t= %d bytes\n", sizeof(double));
  printf ("sizeof (int*)\t= %d bytes\n", sizeof(int*));
}

И проверяем:
gcc ./main.c && ./a.out 
Compiled on 64-bit platform
sizeof (char)   = 1 bytes
sizeof (short)  = 2 bytes
sizeof (int)    = 4 bytes
sizeof (long)   = 8 bytes
sizeof (float)  = 4 bytes
sizeof (double) = 8 bytes
sizeof (int*)   = 8 bytes
64-ёх битная платформа (определена макросом и вычислением sizeof(int*)), и правило

sizeof (short) <= sizeof (int) <= sizeof (long)

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