Я начинающий программист на языке C; Возможно, это наивный вопрос, но, пожалуйста, потерпите меня. Я узнал, что размер хранилища char
, short
, int
, long
, long long
, float
, double
и long double
зависит от реализации и может сильно различаться. Когда я использовал sizeof(long)
на своей машине, он вернул мне 8 bytes
, но когда я использовал ту же функцию на long long
, он также вернул 8 bytes
. Поэтому я подумал, что, поскольку мой процессор основан на X64 ISA, он может поддерживать максимум 64 бита/8 байт. Но когда я использовал ту же функцию для long double
, результат был 16 bytes
.
Почему long double
может иметь ширину 16 байт, а long long
— нет? Если long long
то же самое, что long
, какой смысл в нем? И еще один вопрос: как мой процессор на самом деле справляется с long double
шириной 128 бит, когда размер регистров моего процессора составляет 64 бита?
Дополнительная информация: Я использую gcc (GCC) 12.2.0
, если это как-то связано с компилятором.
#include <stdio.h>
int main(void) {
printf("%d\n", sizeof(long));
printf("%d\n", sizeof(long long));
printf("%d\n", sizeof(long double));
return 0;
}
Результат был следующим:
8
8
16
Есть много соображений. long long
должна быть не ниже 64-битной. Почему вас волнует, является ли long long
64-битным или более широким? Если вам нужен 64-битный тип, используйте (u)int64_t
.
@Greeshma Вы уверены, что регистры с плавающей запятой на вашем процессоре 64-битные?
@chux-ReinstateMonica, если long
и long long
имеют одинаковый размер. Какой нюанс между ними?
@Greeshma: смотри это.
@Greeshma "Какой нюанс между ними?" --> Один типа long
(и всегда не менее 32-битный), другой типа long long
(и всегда не менее 64-битный). long
является поддиапазоном long long
, даже если они пересекаются. long long
может отсутствовать в коде до C-99.
Текущее состояние 128-битных двойников кажется очень запутанным, см. Формат с плавающей запятой четырехкратной точности. Я бы не рассчитывал, что long double
всегда будет такого размера.
В x86-64 имеются отдельные целочисленные регистры и регистры с плавающей запятой. Целочисленные регистры имеют разрядность 64 бита. Регистры с плавающей запятой больше.
Нет никакого смысла использовать long
, если только int
не имеет 16 бит, long
— 32, а long long
— 64. Избегайте этого, где это возможно. Это вызывает путаницу только в том случае, если некоторые компиляторы 2024 года имеют 32-битную версию (MSVC), а другие — 64-битную (GCC) на одной и той же архитектуре.
Почему long double может иметь ширину 16 байт, но не длину?
... как мой процессор действительно обрабатывает long double шириной 128 бит, когда размер регистров моего процессора составляет 64 бита?
ОП ответил сам себе.
размер хранилища ... long long, ... и long double зависят от реализации и могут сильно различаться
В C нет фиксированной связи между long long
и long double
. Размер регистра ЦП только определяет детали реализации, а не управляет ими. 8-битный процессор может иметь 256-битный long double
.
Включите все предупреждения компилятора и используйте "%zu"
при печати size_t
объектов.
// printf("%d\n", sizeof(long double));
printf("%zu\n", sizeof(long double));
ОП ответил сам себе. - Я не вижу никаких упоминаний о 80-битном типе x87, кроме моего ответа, который я только что опубликовал. Это то, что long double
есть в x86-64 System V, а не дабл-дабл или какой-либо другой тип со 128 битами значения. Так что, похоже, это едва ли отвечает на вопрос.
Существует три распространенные аппаратные реализации типа «long double» языка C или C++:
64-битный формат с плавающей запятой двойной точности IEEE 754: long double дает те же результаты, что и double. Обычно используется в ARM, часто используется в PowerPC и POWER.
80-битная плавающая запятая расширенной точности IEEE 754: 64-битная мантисса и 15 битов экспоненты, что дает ошибки округления 11 бит (2048 раз) меньше двойной точности, с 4 дополнительными битами экспоненты, охватывающими диапазон до 10^4000. Иногда хранится в 80 битах, часто в 128 битах с 48 неиспользуемыми битами. Часто встречается на процессорах Intel и AMD x86.
«double double», представляющий число с плавающей запятой в виде пары двух чисел двойной точности x, y, где y настолько мало, что сложение x+y двойной точности дает в качестве результата x. Мантисса составляет около 106 бит, диапазон экспоненты такой же, как и для двойной точности. Берёт 128 бит. Довольно часто встречается на процессорах PowerPC и POWER. Может быть реализован достаточно быстро на процессорах с быстрой операцией умножения-сложения. Несовместим с IEEE 754.
В x86-64 System V ABI (используется в Linux, BSD, macOS и т. д.) long double
— это 80-битный формат x87 (так же, как в i386 System V). Фактические данные занимают 10 байт, остальное — заполнение для выравнивания.
(В стандартной Windows x64 long double
использует тот же формат, что и double
, IEEEbinary64, поэтому нет доступа к устаревшей аппаратной поддержке расширенной точности FP, только то, что можно сделать с помощью SSE/SSE2. GCC позволяет -mlong-double-80
, и это на самом деле по умолчанию для GCC, поэтому по умолчанию он не совместим с ABI с MSVC !! См. также Использовал ли какой-либо компилятор 80-битную плавающую запятую Intel x87 на Retrocomputing.SE.)
Разработчики ABI x86-64 решили увеличить его long double
до 16, чтобы его можно было более эффективно копировать (например, с помощью SSE alignof
), а также, возможно, повысить эффективность в реальных x87 movaps
/fld
, избегая разделения строк кэша1.
i386 System V использовал fstp
, поэтому alignof(long double) = 4
с sizeof(long double) == 12
на x86-64/i386 GNU/Linux. В этом случае всего 2 байта заполнения. gcc -m32
должно быть кратно sizeof(T)
и должно быть достаточно большим, чтобы вместить все биты значения.
Фактические процессоры 386 не заботились о выравнивании больше 4, поэтому для них это не было проблемой, когда разрабатывался ABI. Но процессоры P6 и K7 заботились об этом (которые были актуальны на момент разработки x86-64 SysV), и у них были худшие штрафы за перекос, чем у современных процессоров. (См. Почему Windows64 использует другое соглашение о вызовах, чем все другие ОС на x86-64? некоторые ссылки на архивы списка рассылки x86-64.org, где это обсуждалось, а также другие дизайнерские решения.)
FP alignof(T)
совершенно не связан с целым числом long double
. C требует, чтобы его ширина была не менее 64 бит (на самом деле это требование диапазона значений, но оно также должно быть двоичным). 64-битные целые числа естественным образом подходят для x86-64, поскольку это ширина целочисленного регистра. Если сделать long long
еще шире, это замедлит любой код, который его использует (требуется long long
/add
для adc
и расширение +
+ 2x нерасширения mul
для умножения.)
imul
также является 64-битным в x86-64 System V, но C требует, чтобы он был не менее 32 бит (именно это выбрала Windows x64, так что это реальная проблема переносимости, если вам нужен тип, достаточно широкий для хранения указателей). например).
Во многих старых кодах long
используется для переменных, которые должны быть как минимум 64-битными, но которым невыгодно быть 128-битными или шире. (В некоторых новых кодах для этого используются long long
или int_least64_t
, хотя многие из более узких типов int_fast64_t
не подходят для большинства целей из-за неправильного выбора на основных платформах, таких как x86-64 GNU/Linux — размер int_fast8_t против размера int_fast16_t на x86-64 платформа — все они становятся 64-битными, за исключением fast
, тратя огромное количество места и занимаемого кэша в массивах или структурах.)
Таким образом, ожидается, что такие типы, как fast8
и long
, выбираются как эффективные размеры для целевой ISA, а не с расширенной точностью (больше регистра), если только это не необходимо для удовлетворения требований минимального диапазона значений в стандарте C.
Эта система long long
/int
/long
не самая удобная для написания переносимого эффективного кода. Как только вычислительная среда установилась на машинах с 8-битными байтами и обычно 32- или 64-битными регистрами, C вводит такие типы, как long long
и int32_t
(необязательно, но если они присутствуют, они также должны быть дополнением до 2, а не 1 или знаком/величиной) . И теперь в С23, int64_t
. Поэтому, если вы знаете, что 32- или 64-битной версии достаточно, вы можете явно запросить этот размер.
Сноска 1: IDK, если разделение строк кэша для 80-битной версии x87 является настоящей проблемой для современных процессоров. Я думаю, что два мопа порта загрузки _BitInt(128)
на современном Intel, вероятно, представляют собой 64-битную загрузку мантиссы и 16-битную загрузку экспоненты + знака, а остальные мопы необходимы для их объединения.
Так что fld
, возможно, сегодня было бы достаточно, но это все равно увеличило бы размер alignof(long double) == 8
. Разделение строк кэша по-прежнему может привести к двум промахам в кэше, что делает произвольный доступ к большому холодному массиву хуже, чем фактический дизайн.
Но трата 6 из каждых 16 байтов на заполнение ухудшает объем кэша и ухудшает пропускную способность памяти для последовательного доступа. Таким образом, ни один вариант ABI не является оптимальным для всех случаев использования.
C очень рано определил размеры типов как «не менее x бит», независимо от ширины адресной шины, шины данных, регистров, ALU и т. д.
C также очень рано определил, что определенные целочисленные типы гарантированно не будут иметь меньше битов, чем некоторые другие целочисленные типы: long >= int >= short >= char.
Вот почему даже 8-битный микроконтроллер, такой как 8051 (имеющий только один 16-битный регистр), может поддерживать long: но это должно быть сделано с использованием множества инструкций для перетасовки данных между ОЗУ и регистрами для достижения 32-битного процессора. битовая арифметика.
C также добавил, что long long >= long.
Тот же принцип применим и к типам с плавающей запятой: long double >= double >= float.
Обратите внимание, что C не делает конкретной корреляции между целочисленными типами и типами с плавающей запятой, и это, по сути, то, что вы обнаружили.
Для типов с плавающей запятой стандарт фактически позволяет реализации компилятора решать, следует ли использовать все доступные биты, а также количество битов, используемых для экспоненты и мантиссы соответственно. Обычно это отличается только для типов long double.
Однако из соображений предсказуемости в стандарт C99 были добавлены целые числа фиксированной ширины (intN_t, uintN_t).
Подробности см. https://en.wikipedia.org/wiki/C_data_types и https://en.wikipedia.org/wiki/Long_double.
@chux-ReinstateMonica, мне интересно, почему мой компилятор также не выделил 16 байтов надолго?