2013-03-27 2 views
7

Я прохожу через книгу практического программирования программирования О'Рейли и прочитал книгу K & R на языке программирования C, и я действительно испытываю трудности с пониманием концепции союзов.В чем смысл профсоюзов в C?

Они берут размер самого большого типа данных, который заставляет их ... и последний назначенный один перезаписывает остальные ... но почему бы просто не использовать/освобождать память по мере необходимости?

В книге упоминается, что она используется в общении, где вам нужно установить флаги того же размера; и на веб-сайте googled, что он может устранить куски памяти нечетного размера ... но может ли он использоваться в современном, не встроенном пространстве памяти?

Есть ли что-то лукавое, что вы можете с ним сделать и регистры процессора? Это просто удержание от более ранней эпохи программирования? Или это, как и печально известный goto, по-прежнему обладает мощным использованием (возможно, в узких пространствах памяти), что заставляет его стоять?

+1

Посмотрите на [Type-каламбурная] (http://en.wikipedia.org/wiki/Type_punning). – Mysticial

+0

Это может быть полезно в ситуациях, когда вам нужно преобразовать структуру в ее двоичные значения (например, char []), не используя хаки указателей. –

+0

Возможный дубликат [C/C++: Когда кто-нибудь будет использовать союз? Это в основном остаток от C дней?] (Http: // stackoverflow.com/questions/4788965/cc-when-would-any-use-a-union-is-it-basic-a-remnant-from-the-c-only) –

ответ

5

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

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

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

Если вы используете классический struct, это будет 8 байтов (по крайней мере, если вам не повезло, есть и заполняющие байты). Используя union, это всего лишь 4 байта. Таким образом, вы сохраняете 50% памяти, что не так много для одного экземпляра, но представьте, что у вас миллион.

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

1

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

например. вы можете иметь логическое «isDouble» и объединение «doubleOrLong», которое имеет как двойной, так и длинный. Если isDouble == true интерпретирует объединение как двойное другое, оно интерпретирует его как длинное.

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

Вам это действительно не нужно в настоящее время, так как память настолько дешевая, но во встроенных системах она использует ее.

+1

Даже в современных машинах с тоннами гигабайт оперативной памяти это не будет мешать экономии памяти на основе вашего сценария, например при сохранении чего-либо на диске, при отправке чего-либо по сети или просто на основе количества наборов данных. – Mario

+1

@Mario Согласен. Аргумент о том, что память дешевая, и вам не нужно заботиться о размере структуры, всегда пахнет ленивым дизайном для меня. Предположим, вы хотите сохранить сто миллионов записей в памяти, а в записях имеется 32 разных булевских флага. Вы используете 32 логических значения или 32-битное целое число XOR'd. Без учета заполнения, это разница примерно в 3 ГБ. – Anthony

0

В Windows API очень много используются профсоюзы. LARGE_INTEGER - пример такого использования. В принципе, если компилятор поддерживает 64-битные целые числа, используйте элемент QuadPart; в противном случае установите низкий DWORD и высокий DWORD вручную.

0

Это не удержание, поскольку язык C был создан в 1972 году, когда память была реальной проблемой.

Вы можете сделать аргумент, что в современном, не встроенном пространстве вы можете не использовать C в качестве языка программирования для начала. Если вы выбрали C как ваш язык для реализации, вы хотите использовать преимущества C: он эффективен, близок к металлу, что приводит к жестким, быстрым двоичным файлам.

Таким образом, при выборе использования C вы все равно хотите воспользоваться преимуществами, которые включают в себя эффективность памяти. На что Союз работает очень хорошо; позволяя вам иметь некоторую степень безопасности типов, обеспечивая при этом доступ к самой маленькой печатной копии памяти.

+0

Вообще-то, моя причина вернуться к просмотру C, затем C++, затем Templating на C++, затем CLR, затем C++/CLI, потому что я пытаюсь создать обновление индекса в 64-разрядном (или выше) индексе .Net List/Dictionary классы и связанные классы/пространства имен. У меня есть зуд, чтобы поцарапать, так что я просто собираюсь «исправить» то, что я считаю сломанным здесь. – user978122

0

Одно место, где я видел его, используется в реализации Doom 3/idTech 4 Fast Inverse Square Root.

Для тех, кто не знаком с этим алгоритмом, он по существу требует обработки числа с плавающей точкой как целого. Старый Quake (и ранее) версия кода делает это следующим образом:

float y = 2.0f; 

// treat the bits of y as an integer 
long i = * (long *) &y; 

// do some stuff with i 

// treat the bits of i as a float 
y = * (float *) &i; 

original source on GitHub

Этот код берет адрес число с плавающей точкой y, отбрасывает его в указатель на длинное (т. е. 32-битное целое число в Quake-днях) и разыгрывает его в i. Затем он делает некоторые невероятно странные бит-twiddling вещи, и наоборот.

Есть два недостатка в этом. Один из них состоит в том, что запутанный процесс адресации, литья, разыменования заставляет значение y считываться из памяти, а не из регистра , а также на обратном пути. Однако на компьютерах эпохи Quake регистры с плавающей точкой и целые регистры были полностью разделены, поэтому вам в значительной степени пришлось нажать на память и вернуться, чтобы справиться с этим ограничением.

Второе, что, по крайней мере, на C++, выполнение такого каста глубоко осуждается, даже если делать то, что составляет вуду, например, эта функция делает. Я уверен, что есть более веские аргументы, но я не уверен, что это такое :)

Итак, в Doom 3 идентификатор включал следующий бит в свою новую реализацию (в которой используется другой набор бит-скручиваний, но подобная идея):

union _flint { 
     dword     i; 
     float     f; 
}; 

... 
union _flint seed; 
seed.i = /* look up some tables to get this */; 
double r = seed.f; // <- access the bits of seed.i as a floating point number 

original source on GitHub

Теоретически, на машине SSE2, это могут быть доступны через один регистр; На практике я не уверен, сделает ли какой-нибудь компилятор. Это, по-моему, еще более чистый код, чем игра в кастинг в более ранней версии Quake.


- игнорирование "достаточно продвинутые компилятор" аргументы

Смежные вопросы