Союзы против структур в C - PullRequest
       21

Союзы против структур в C

9 голосов
/ 07 апреля 2009

Идея, стоящая за этим вопросом, состоит в том, чтобы понять более глубокие концепции использования объединения и использования его по-другому, чтобы сэкономить память. Мой вопрос ко всем -

скажем, есть структура

struct strt
{
   float f;
   char c;
   int a;
}

и та же структура, представленная в объединении

union unin
{
   float f;
   char c;
   int a;
}

Если я присваиваю значения элементам структуры один за другим, а затем печатаю их, он получает распечатаны. Но в случае объединения это не происходит, выполняется некоторая перезапись ..

Итак, мне нужно найти метод, который может хранить значения f, c, объединения с использованием, а затем я могу напечатать его. (Применяйте любые операции или что-нибудь в этом роде ...), но я нахожусь в поиске этой техники. Кто-нибудь может мне помочь или дать какую-нибудь идею?

Ответы [ 6 ]

49 голосов
/ 07 апреля 2009

Если бы вы посмотрели, как структура хранит свои значения, это было бы примерно так:

|0---1---2---3---|4---|5---6---7---8---|
|ffffffffffffffff|    |                | <- f: Where your float is stored
|                |cccc|                | <- c: Where your char is stored
|                |    |aaaaaaaaaaaaaaaa| <- a: Where your int is stored

Таким образом, когда вы меняете значение f, вы на самом деле меняете байты 0-3. Когда вы меняете свой символ, вы на самом деле меняете байт 4. Когда вы меняете свой int, вы на самом деле меняете байты 5-8.

Если вы посмотрите, как объединение хранит свои значения, это будет примерно так:

|0---1---2---3---|
|ffffffffffffffff| <- f: where your float is stored
|cccc------------| <- c: where your char is stored
|aaaaaaaaaaaaaaaa| <- a: where your int is stored

Так что теперь, когда я изменяю значение f, я меняю байты 0-3. Поскольку c хранится в байте 0, при изменении f вы также изменяете c и a! Когда вы меняете c, вы меняете части f и a - и когда вы меняете a, вы меняете c и f. Вот где происходит ваша «перезапись». Когда вы упаковываете 3 значения в один адрес памяти, вы совсем не «экономите место»; вы просто создаете 3 разных способа просмотра и изменения одних и тех же данных. На самом деле у вас нет int, float и char в этом объединении - на физическом уровне у вас есть только 32 бита, которые можно рассматривать как int, float или char. Изменение одного означает означает , чтобы изменить другие. Если вы не хотите, чтобы они меняли друг друга, используйте структуру.

Вот почему gcc сообщает вам, что ваша структура имеет длину 9 байт, а объединение - только 4 - это не экономит место - просто структуры и объединения не одно и то же.

38 голосов
/ 07 апреля 2009

Я думаю, вы неправильно поняли цель union.

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

С вашим союзом, когда вы пишете:

union foo;
foo.c = 3;

Тогда foo.a и foo.f будут изменены. Это связано с тем, что .a, .c и .f хранятся в одной и той же ячейке памяти . Таким образом, каждый член объединения представляет собой разные «представления» одной и той же памяти. Этого не происходит с struct, потому что все члены различны и отделены друг от друга.

Нет способа обойти это поведение , потому что оно намеренно.

12 голосов
/ 07 апреля 2009

Я думаю, вы неправильно понимаете Союзы.

Идея использования союзов заключается в том, чтобы сэкономить память ...

да, это одна из причин

... и получить результат, эквивалентный структуре ...

нет

нет

это не эквивалентно. Они похожи в исходном коде, но это совсем другое. Как яблоки и самолеты.

Союзы - это очень, очень низкоуровневая конструкция, которая позволяет вам видеть часть памяти, как будто хранит любой из ее "членов", но вы можете использовать только по одному . Даже использование слова «участник» вводит в заблуждение. Их следует называть «представлениями» или чем-то, а не членами.

Когда вы пишете:

union ABCunion
{
    int a;
    double b;
    char c;
} myAbc;

Вы говорите: «возьмите кусок памяти, достаточно большой для наибольшего числа из числа int, char и double, и давайте назовем его myAbc .

В этой памяти теперь вы можете хранить или int, или double, или char. Если вы храните int, а затем сохраняете double, int исчезает навсегда.

Какой смысл тогда?

Существует два основных варианта использования Союзов.

а) Дискриминационное хранилище

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

Типичным (явным) примером будет:

struct MyVariantType
{
    int typeIndicator ;  // type=1 -> It's an int, 
                         // type=2 -> It's a  double, 
                         // type=3 -> It's a  char
    ABCunion body;
};

Например, "Варианты" VB6 - это Союзы, не отличающиеся от вышеупомянутых (но более сложные).

б) Разделенное представление Это иногда полезно, когда вам нужно видеть переменную как «целое» или как комбинацию частей. Это проще объяснить на примере:

union DOUBLEBYTE
{
    struct
    {
        unsigned char a;
        unsigned char b;
    } bytes;
    short Integer;        
} myVar;

Вот короткое int, объединенное с парой байтов. Теперь вы можете просмотреть то же значение, что и короткое int (myVar.Integer), или вы можете так же легко изучить отдельные байты, которые составляют часть значения (myVar.bytes.a и myVar.bytes.b).

Обратите внимание, что это второе использование не переносимо (я уверен); это означает, что не гарантируется работа на разных архитектурах машин; но это использование абсолютно необходимо для задач, для которых был разработан язык C (реализация ОС).

9 голосов
/ 07 апреля 2009

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

В вашем конкретном примере вы можете хранить в объединении float ( f ), char ( c ) или int ( a ). Однако память будет выделена только для самого большого элемента в объединении. Все элементы в объединении будут использовать одну и ту же часть памяти . Другими словами, запись одного значения в объединение, за которым следует другое, приведет к перезаписи первого значения.

Вам нужно вернуться и спросить себя что вы моделируете :

  • Действительно ли вы хотите, чтобы значения f , c и a были взаимоисключающими (т.е. одновременно может существовать только одно значение) ? Если это так, рассмотрите возможность использования объединения в сочетании со значением перечисления (хранящимся вне объединения), указывающим, какой из членов объединения является «активным» в любой конкретный момент времени. Это позволит вам получить выгоду от использования объединения за счет использования более опасного кода (поскольку любой, кто его поддерживает, должен знать, что значения являются взаимоисключающими, т. Е. Это действительно объединение). Рассматривайте эту опцию, только если вы создаете много таких объединений, и сохранение памяти является жизненно важным (например, для встроенных процессоров). Вы даже можете НЕ экономить память, потому что вам нужно будет создать в стеке переменные перечисления, которые также будут занимать память.

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

Edit:

(Очень простой) пример использования перечислений в сочетании с объединением:

typedef union
{
    float f;
    char c;
    int a;
} floatCharIntUnion;

typedef enum
{
    usingFloat,
    usingChar,
    usingInt
} unionSelection;

int main()
{
    floatCharIntUnion myUnion;
    unionSelection selection;

    myUnion.f = 3.1415;
    selection = usingFloat;
    processUnion(&myUnion, selection);

    myUnion.c = 'a';
    selection = usingChar;
    processUnion(&myUnion, selection);

    myUnion.a = 22;
    selection = usingInt;
    processUnion(&myUnion, selection);
}

void processUnion(floatCharIntUnion* myUnion, unionSelection selection)
{

    switch (selection)
    {
    case usingFloat:
        // Process myUnion->f
        break;
    case usingChar:
        // Process myUnion->c
        break;
    case usingInt:
        // Process myUnion->a
        break;
    }
}
1 голос
/ 07 апреля 2009

Это классический пример использования объединения для хранения данных в зависимости от внешнего маркера.

int, float и char * занимают одно и то же место в объединении, они не являются последовательными, поэтому, если вам нужно сохранить их все, вы ищете структуру, а не объединение.

Структура - это размер самой большой вещи в объединении плюс размер типа, поскольку она находится вне объединения.

#define TYP_INT 0
#define TYP_FLT 1
#define TYP_STR 2

typedef struct {
    int type;
    union data {
        int a;
        float b;
        char *c;
    }
} tMyType;

static void printMyType (tMyType * x) {
    if (x.type == TYP_INT) {
        printf ("%d\n", x.data.a;
        return;
    }
    if (x.type == TYP_FLT) {
        printf ("%f\n", x.data.b;
        return;
    }
    if (x.type == TYP_STR) {
        printf ("%s\n", x.data.c;
        return;
    }
}

Функция printMyType будет правильно определять, что хранится в структуре (если вы не врете ей), и распечатывает соответствующее значение.

Когда вы заполняете один из них, вы должны сделать:

x.type = TYP_INT;
x.data.a = 7;

или

x.type = TYP_STR;
x.data.c = "Hello";

и данный x может быть только одним за раз.

Горе тому, кто пытается:

x.type = TYP_STR;
x.data.a = 7;

Они напрашиваются на неприятности.

0 голосов
/ 07 апреля 2009

Союзы обычно используются, когда только один из приведенных ниже элементов будет сохранен в экземпляре в любой данный момент времени. то есть вы можете хранить плавающее число, символ или int в любой момент. Это для экономии памяти - не выделяя дополнительную / отличную память для float и int, когда вы просто собираетесь использовать ее для хранения char. Объем выделенной памяти = самый большой тип в объединении.

union unin
{
   float f;
   char c;
   int a;
}

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

...