размер объединения в C / C ++ - PullRequest
40 голосов
/ 11 апреля 2009

Каков размер объединения в C / C ++? Это размер самого большого типа данных внутри него? Если так, как компилятор вычисляет, как переместить указатель стека, если активен один из меньших типов данных объединения?

Ответы [ 8 ]

56 голосов
/ 11 апреля 2009

A union всегда занимает столько же места, сколько и самый большой член. Неважно, что в данный момент используется.

union {
  short x;
  int y;
  long long z;
}

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

Примечание : Как отмечает Stefano , фактическое пространство, которое займет любой тип (union, struct, class), зависит от других вопросов, таких как выравнивание компилятором. Я не прошел через это для простоты, так как я просто хотел сказать, что профсоюз принимает во внимание самый большой элемент. Важно знать, что фактический размер зависит от выравнивания .

28 голосов
/ 11 апреля 2009

Стандарт отвечает на все вопросы раздела 9.5 стандарта C ++ или пункта 6.5.2.3 пункта 5 стандарта C99 (или пункта 6 стандарта C11):

В объединении самое большее один из элементов данных может быть активным в любое время, то есть значение не более одного из членов данных может быть сохранено в объединении в любое время. [Примечание: одна специальная гарантия сделана для упрощения использования объединений: если POD-объединение содержит несколько POD-структур, которые имеют общую начальную последовательность (9.2), и если объект этого типа POD-объединения содержит один из POD-структурам разрешается проверять общую начальную последовательность любого из членов POD-структуры; см. 9.2. ] Размер объединения достаточен, чтобы содержать самый большой из его элементов данных. Каждый элемент данных выделяется так, как если бы он был единственным членом структуры.

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

Одно из распространенных применений - лексеры, где у вас могут быть разные токены, но в зависимости от токена у вас есть различная информация для хранения (помещая line в каждую структуру, чтобы показать, какова общая начальная последовательность):

struct tokeni {
    int token; /* type tag */
    union {
        struct { int line; } noVal;
        struct { int line; int val; } intVal;
        struct { int line; struct string val; } stringVal;
    } data;
};

Стандарт позволяет вам получить доступ к line каждого члена, потому что это общая начальная последовательность каждого из них.

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

union float_cast { unsigned short s[2]; float f; };

Это может пригодиться при написании низкоуровневого кода. Если компилятор не поддерживает это расширение, но вы все равно делаете это, вы пишете код, результаты которого не определены. Поэтому убедитесь, что ваш компилятор поддерживает его, если вы используете этот трюк.

16 голосов
/ 11 апреля 2009

Это зависит от компилятора и опций.

int main() {
  union {
    char all[13];
    int foo;
  } record;

printf("%d\n",sizeof(record.all));
printf("%d\n",sizeof(record.foo));
printf("%d\n",sizeof(record));

}

Это выводит:

13 4 16

Если я правильно помню, это зависит от выравнивания, которое компилятор помещает в выделенное пространство. Так что, если вы не используете какую-либо специальную опцию, компилятор поместит отступ в ваше пространство объединения.

edit: с gcc вам нужно использовать директиву pragma

int main() {
#pragma pack(push, 1)
      union {
           char all[13];
           int foo;
      } record;
#pragma pack(pop)

      printf("%d\n",sizeof(record.all));
      printf("%d\n",sizeof(record.foo));
      printf("%d\n",sizeof(record));

}

это выводит

13 4 13

Вы также можете увидеть это из дизассемблирования (убрал часть printf, для ясности)

  0x00001fd2 <main+0>:    push   %ebp             |  0x00001fd2 <main+0>:    push   %ebp
  0x00001fd3 <main+1>:    mov    %esp,%ebp        |  0x00001fd3 <main+1>:    mov    %esp,%ebp
  0x00001fd5 <main+3>:    push   %ebx             |  0x00001fd5 <main+3>:    push   %ebx
  0x00001fd6 <main+4>:    sub    $0x24,%esp       |  0x00001fd6 <main+4>:    sub    $0x24,%esp
  0x00001fd9 <main+7>:    call   0x1fde <main+12> |  0x00001fd9 <main+7>:    call   0x1fde <main+12>
  0x00001fde <main+12>:   pop    %ebx             |  0x00001fde <main+12>:   pop    %ebx
  0x00001fdf <main+13>:   movl   $0xd,0x4(%esp)   |  0x00001fdf <main+13>:   movl   $0x10,0x4(%esp)                                         
  0x00001fe7 <main+21>:   lea    0x1d(%ebx),%eax  |  0x00001fe7 <main+21>:   lea    0x1d(%ebx),%eax
  0x00001fed <main+27>:   mov    %eax,(%esp)      |  0x00001fed <main+27>:   mov    %eax,(%esp)
  0x00001ff0 <main+30>:   call  0x3005 <printf>   |  0x00001ff0 <main+30>:   call   0x3005 <printf>
  0x00001ff5 <main+35>:   add    $0x24,%esp       |  0x00001ff5 <main+35>:   add    $0x24,%esp
  0x00001ff8 <main+38>:   pop    %ebx             |  0x00001ff8 <main+38>:   pop    %ebx
  0x00001ff9 <main+39>:   leave                   |  0x00001ff9 <main+39>:   leave
  0x00001ffa <main+40>:   ret                     |  0x00001ffa <main+40>:   ret    

Где единственная разница в main + 13, где компилятор размещает в стеке 0xd вместо 0x10

10 голосов
/ 11 апреля 2009

Не существует понятия активного типа данных для объединения. Вы можете свободно читать и писать любому «члену» профсоюза: вам решать, что вы получите.

Следовательно, размер объединения всегда равен размеру его самого большого типа данных.

3 голосов
/ 11 апреля 2009

Размер будет, по крайней мере, самого большого типа сочинения. Не существует понятия «активный» тип.

2 голосов
/ 11 апреля 2009

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

Вы часто видите, что это используется в сочетании с вызовами ioctl () в Unix, все вызовы ioctl () будут проходить одну и ту же структуру, которая содержит объединение всех возможных ответов. Например. этот пример взят из /usr/include/linux/if.h, и эта структура используется в ioctl () для конфигурирования / запроса состояния интерфейса Ethernet, параметры запроса определяют, какая часть объединения фактически используется:

struct ifreq 
{
#define IFHWADDRLEN 6
    union
    {
        char    ifrn_name[IFNAMSIZ];        /* if name, e.g. "en0" */
    } ifr_ifrn;

    union {
        struct  sockaddr ifru_addr;
        struct  sockaddr ifru_dstaddr;
        struct  sockaddr ifru_broadaddr;
        struct  sockaddr ifru_netmask;
        struct  sockaddr ifru_hwaddr;
        short   ifru_flags;
        int ifru_ivalue;
        int ifru_mtu;
        struct  ifmap ifru_map;
        char    ifru_slave[IFNAMSIZ];   /* Just fits the size */
        char    ifru_newname[IFNAMSIZ];
        void *  ifru_data;
        struct  if_settings ifru_settings;
    } ifr_ifru;
};
0 голосов
/ 12 сентября 2016

Каков размер объединения в C / C ++? Это размер самого большого тип данных внутри него?

Да , Размер объединения - это размер его самого большого члена.

Например:

#include<stdio.h>

union un
{
    char c;
    int i;
    float f;
    double d;
};

int main()
{
    union un u1;
    printf("sizeof union u1 : %ld\n",sizeof(u1));
    return 0;
}

Выход:

sizeof union u1 : 8
sizeof double d : 8

Здесь самый большой член - double. Оба имеют размер 8. Итак, как правильно сказал sizeof, размер объединения действительно равен 8.

как компилятор вычисляет, как перемещать указатель стека, если он меньшего типа данных объединения является активным?

Внутренне обрабатывается компилятором. Предположим, что мы обращаемся к одному члену данных union, тогда мы не можем получить доступ к другому члену данных, поскольку мы можем получить доступ к одному члену данных union, поскольку каждый член данных совместно использует одну и ту же память. Используя Union, мы можем сэкономить много ценного пространства.

0 голосов
/ 11 апреля 2009
  1. Размер самого большого члена.

  2. Вот почему союзы обычно имеют смысл внутри структуры, которая имеет флаг, указывающий, какой элемент является «активным».

Пример:

struct ONE_OF_MANY {
    enum FLAG { FLAG_SHORT, FLAG_INT, FLAG_LONG_LONG } flag;
    union { short x; int y; long long z; };
};
...