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)

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