Подтвердить что ты не робот

Sizeof union в C/С++

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

4b9b3361

Ответ 1

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

В объединении самое большее один из членов данных может быть активным в любое время, то есть значение самое большее одного из членов данных может быть сохранено в объединении в любое время. [Примечание: одна специальная гарантия сделана для того, чтобы упростить использование объединений: если 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; };

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

Ответ 2

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

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

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

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

Ответ 3

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

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    

В тех случаях, когда единственная разница в главном + 13, где компилятор выделяет в стеке 0xd вместо 0x10

Ответ 4

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

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

Ответ 5

Размер будет, по крайней мере, самого крупного типа компоновки. Нет понятия "активного" типа.

Ответ 6

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

Вы часто видите, что это используется в сочетании с вызовами 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;
};

Ответ 7

  • Размер самого большого члена.

  • Вот почему профсоюзы обычно имеют смысл внутри структуры, которая имеет флаг, который указывает, кто является "активным" членом.

Пример:

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

Ответ 8

Каков размер объединения в 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, мы можем сэкономить много полезного пространства.