Объединения

Содержание

Объединение — это место в памяти, которое используется для хранения переменных, разных типов. Объединение дает возможность интерпретировать один и тот же набор битов не менее, чем двумя разными способами. Объявление объединения (начинается с ключевого слова union) похоже на объявление структуры и в общем виде выглядит так:

union тег {
  тип имя-члена;
  тип имя-члена;
  тип имя-члена;
  .
  .
  .
} переменные-этого-объединения;

Например:

union u_type {
  int i;
  char ch;
};

Это объявление не создает никаких переменных. Чтобы объявить переменную, ее имя можно поместить в конце объявления или написать отдельный оператор объявления. Чтобы с помощью только что написанного кода объявить переменную-объединение, которая называется cnvt и имеет тип u_type, можно написать следующий оператор:

union u_type cnvt;

В cnvt одну и ту же область памяти занимают целая переменная i и символьная переменная ch. Конечно, i занимает 2 байта (при условии, что целые значения занимают по 2 байта), a ch — только 1. На рис. 7.2 показано, каким образом i и ch пользуются одним и тем же адресом. В любом месте программы хранящиеся в cnvt данные можно обрабатывать как целые или символьные.

|<------ i ------>|
|                 |
+--------+--------+
| Байт 0 | Байт 1 |
+--------+--------+
|        |
|<- ch ->|

Рис. 7.2. Как i, так и ch, хранятся в объединении cnvt (подразумевается, что целые значения занимают по 2 байта)

Когда переменная объявляется с ключевым словом union, компилятор автоматически выделяет столько памяти, чтобы в ней поместился самый большой член нового объединения. Например, при условии, что целые значения занимают по 2 байта, для размещения i в cnvt необходимо, чтобы длина этого объединения составляла 2 байта, даже если для ch требуется только 1 байт.

Для получения доступа к члену объединения используйте тот же синтаксис, что и для структур: операторы точки и стрелки. При работе непосредственно с объединением следует пользоваться точкой. А при получении доступа к объединению с помощью указателя нужен оператор стрелка. Например, чтобы присвоить целое значение 10 элементу i из cnvt, напишите

cnvt.i = 10;

В следующем примере функции func1 передается указатель на cnvt:

void func1(union u_type *un)
{
  un->i = 10; /* присвоение cnvt значение 10 с помощью указателя */
}

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

Чтобы получить представление о полезности объединений в случаях, когда нужны нестандартные преобразования типа, подумайте над проблемой записи целых значений типа short в файл, который находится на диске.

В стандартной библиотеке языка С не определено никакой функции, специально предназначенной для выполнения этой записи.

Хотя данные любого типа можно записывать в файл, пользуясь функцией fwrite(), но было бы нерационально применять этот способ для такой простой операции, как запись на диск целых значений типа short, так как получится чрезмерный перерасход ресурсов. А вот, используя объединение, можно легко создать функцию putw(), которая по одному байту будет записывать в файл двоичное представление целого значения типа short. (В этом примере предполагается, что такие значения имеют длину 2 байта каждое.) Чтобы увидеть, как это делается, вначале создадим объединение, состоящее из целой переменной типа short и из массива 2-байтовых символов:

union pw {
  short int i;
  char ch[2];
};

Теперь с помощью pw можно написать вариант putw(), приведенный в следующей программе.

#include <stdio.h>
#include <stdlib.h>

union pw {
  short int i;
  char ch[2];
};

int putw(short int num, FILE *fp);

int main(void)
{
  FILE *fp;

  fp = fopen("test.tmp", "wb+");
  if(fp == NULL) {
    printf("Файл не открыт.\n");
    exit(1);
  }

  putw(1025, fp);  /* запись значения 1025 */
  fclose(fp);

  return 0;
}

int putw(short int num, FILE *fp)
{
  union pw word;

  word.i = num;

  putc(word.ch[0], fp); /* записать первую половину */
  return putc(word.ch[1], fp); /* записать вторую половину */
}

Хотя функция putw() и вызывается с целым аргументом типа short, ей для выполнения побайтовой записи в файл на диске все равно приходится использовать стандартную функцию putc().