Как «длинный двойной» занимает 16 байт/128 бит памяти, когда мой процессор (X64 ISA)?

Я начинающий программист на языке 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

@chux-ReinstateMonica, мне интересно, почему мой компилятор также не выделил 16 байтов надолго?

Greeshma 26.07.2024 22:37

Есть много соображений. long long должна быть не ниже 64-битной. Почему вас волнует, является ли long long 64-битным или более широким? Если вам нужен 64-битный тип, используйте (u)int64_t.

chux - Reinstate Monica 26.07.2024 22:40

@Greeshma Вы уверены, что регистры с плавающей запятой на вашем процессоре 64-битные?

chux - Reinstate Monica 26.07.2024 22:44

@chux-ReinstateMonica, если long и long long имеют одинаковый размер. Какой нюанс между ними?

Greeshma 26.07.2024 22:47

@Greeshma: смотри это.

500 - Internal Server Error 26.07.2024 22:50

@Greeshma "Какой нюанс между ними?" --> Один типа long (и всегда не менее 32-битный), другой типа long long (и всегда не менее 64-битный). long является поддиапазоном long long, даже если они пересекаются. long long может отсутствовать в коде до C-99.

chux - Reinstate Monica 26.07.2024 22:50

Текущее состояние 128-битных двойников кажется очень запутанным, см. Формат с плавающей запятой четырехкратной точности. Я бы не рассчитывал, что long double всегда будет такого размера.

Mark Ransom 26.07.2024 23:07

В x86-64 имеются отдельные целочисленные регистры и регистры с плавающей запятой. Целочисленные регистры имеют разрядность 64 бита. Регистры с плавающей запятой больше.

Raymond Chen 26.07.2024 23:18

Нет никакого смысла использовать long, если только int не имеет 16 бит, long — 32, а long long — 64. Избегайте этого, где это возможно. Это вызывает путаницу только в том случае, если некоторые компиляторы 2024 года имеют 32-битную версию (MSVC), а другие — 64-битную (GCC) на одной и той же архитектуре.

Weather Vane 27.07.2024 02:06
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
9
103
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Почему 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 битами значения. Так что, похоже, это едва ли отвечает на вопрос.

Peter Cordes 26.07.2024 23:39

Существует три распространенные аппаратные реализации типа «long double» языка C или C++:

  1. 64-битный формат с плавающей запятой двойной точности IEEE 754: long double дает те же результаты, что и double. Обычно используется в ARM, часто используется в PowerPC и POWER.

  2. 80-битная плавающая запятая расширенной точности IEEE 754: 64-битная мантисса и 15 битов экспоненты, что дает ошибки округления 11 бит (2048 раз) меньше двойной точности, с 4 дополнительными битами экспоненты, охватывающими диапазон до 10^4000. Иногда хранится в 80 битах, часто в 128 битах с 48 неиспользуемыми битами. Часто встречается на процессорах Intel и AMD x86.

  3. «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.

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