размер на обединение в C/C++

Какъв е размерът на обединението в C/C++? Това ли е размерът на най-големия тип данни в него? Ако е така, как компилаторът изчислява как да премести указателя на стека, ако един от по-малките типове данни на обединението е активен?


person Naveen    schedule 11.04.2009    source източник


Отговори (8)


Стандартът отговаря на всички въпроси в раздел 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; };

Това може да бъде доста полезно, когато пишете код на ниско ниво. Ако компилаторът не поддържа това разширение, но въпреки това го правите, пишете код, чиито резултати не са дефинирани. Така че бъдете сигурни, че вашият компилатор го поддържа, ако използвате този трик.

person Johannes Schaub - litb    schedule 11.04.2009
comment
Ужасен пример за лош стандартен език IMHO - всъщност целият раздел за съюзите изглежда малко оскъден. Защо изобщо да въвеждаме понятието активен? - person ; 11.04.2009
comment
GCC поне изрично подкрепя кръстосаното четене на членовете на синдиката. и ако членовете са свързани по някакъв начин според 3.10/15 или са съвместими с оформлението, почти съм сигурен, че все още можете да прочетете другия член, дори и да не е активният. - person Johannes Schaub - litb; 12.04.2009
comment
Това е активният бит, който ме хваща. ако 9,5\1 трябваше да започне със стойността най-много, тогава нямаше да има нужда да се въвежда тази мъглява концепция за активно. Но това трябва (ако има) да е на comp.lang.c++.std, а не в ужасно поле за коментар SO! Така че се отписвам в тази тема. - person ; 12.04.2009
comment
хаха, добре. пусни нишка там и да се забавляваме :p - person Johannes Schaub - litb; 12.04.2009
comment
@anon active е от съществено значение тук, защото типът на данните в обединението зависи от това, което последно е било съхранено в него. Това, че не разбирате стандарта, не означава, че е грешен. - person Jim Balter; 02.05.2014

union винаги заема толкова място, колкото най-големият член. Няма значение какво се използва в момента.

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

Екземпляр на горния union винаги ще вземе поне long long за съхранение.

Странична бележка: Както е отбелязано от Стефано , действителното пространство, което всеки тип (union, struct, class) ще заеме, зависи от други проблеми, като например подравняване от компилатора. Не минах през това за простота, тъй като просто исках да кажа, че съюзът взема предвид най-големия елемент. Важно е да знаете, че действителният размер наистина зависи от подравняването.

person mmx    schedule 11.04.2009
comment
Едно място, където sizeof може да върне нещо по-голямо, е когато се използва long double. Дългото двойно е 10-байта, но Intel препоръчва подравняване на 16-байта. - person dreamlax; 16.04.2009
comment
long double е ... добре, зависи от компилатора. Мисля, че PowerPC компилаторите използват 128-битови дълги удвоявания - person mmx; 16.04.2009
comment
Да, опа, исках да кажа дълго двойно на x86. - person dreamlax; 16.04.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

Ако си спомням правилно, зависи от подравняването, което компилаторът поставя в разпределеното пространство. Така че, освен ако не използвате някаква специална опция, компилаторът ще постави подложка във вашето пространство за обединение.

редактиране: с gcc трябва да използвате прагма директива

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

person Stefano Borini    schedule 11.04.2009
comment
Благодаря ви - спестява ми необходимостта да сглобявам този отговор! - person Jonathan Leffler; 11.04.2009
comment
Да, предполагам, че всички трябваше да кажем поне толкова голям, колкото най-големият съдържащ се тип. - person ; 11.04.2009
comment
@Neil: подравняването на компилатора е съвсем различен проблем. Случва се и в структури и също зависи от мястото, на което сте поставили обединението в структурата. Въпреки че това със сигурност е вярно, мисля, че просто усложнява отговора на този въпрос. между другото, внимавах да подравня моя примерен съюз към границата от 8 байта :-p - person mmx; 12.04.2009

Няма понятие за активен тип данни за обединение. Вие сте свободни да четете и пишете всеки „член“ на съюза: от вас зависи да тълкувате това, което получавате.

Следователно размерът на един съюз винаги е размерът на неговия най-голям тип данни.

person mouviciel    schedule 11.04.2009
comment
Разбира се, грешите ... езикът на стандарта изрично се отнася до активния тип данни. Въпреки това, sizeof е операция по време на компилиране и така, разбира се, не зависи от активния тип данни. - person Jim Balter; 02.05.2014
comment
@JimBalter - Прав си за стандарта. Това, което имам предвид, е, че в C не можете да правите запитване към обединение относно неговия активен тип данни. Нищо не пречи на кодера да напише float и да прочете int (и да получи боклук). - person mouviciel; 02.05.2014
comment
Казахте, че няма понятие за активен тип данни за обединение. Вие грешахте; го притежавате. Няма да е подходящо да твърдите, че сте имали предвид нещо много различно от това, което сте написали, само за да се опитате да избегнете грешката. Нищо не пречи на кодера да напише float и да прочете int (и да получи боклук). -- Разбира се, нищо не го пречи ... стандартът C не пречи нищо; то само ви казва дали това поведение е дефинирано -- не е. Както многократно беше отбелязано, UB включва всичко, дори детонация на ядрено оръжие. За някои хора това им пречи да кодират UB. - person Jim Balter; 03.05.2014
comment
-1 за това, че не уточнявате дали говорите за C или C++, които се различават фундаментално в това отношение на обединяването на типове. Повторното тълкуване на представянето на обектни байтове е разрешено в C stackoverflow.com/q/11639947/2757035, но не в C++. В последното това е чисто UB или дефинирано от внедряването, ако се задоволите с това (в g++, например, те използват C правилата). - person underscore_d; 14.06.2016

Размерът ще бъде поне този на най-големия тип композиране. Няма концепция за "активен" тип.

person Community    schedule 11.04.2009
comment
Освен че да, има. - person underscore_d; 14.06.2016

Наистина трябва да гледате на обединението като на контейнер за най-големия тип данни вътре в него, комбиниран с пряк път за каст. Когато използвате един от по-малките членове, неизползваното пространство все още е там, но просто остава неизползвано.

Често виждате това да се използва в комбинация с 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;
};
person amo-ej1    schedule 11.04.2009

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

person msc    schedule 12.09.2016

  1. Размерът на най-големия член.

  2. Ето защо обединенията обикновено имат смисъл в структура, която има флаг, който показва кой е "активният" член.

Пример:

struct ONE_OF_MANY {
    enum FLAG { FLAG_SHORT, FLAG_INT, FLAG_LONG_LONG } flag;
    union { short x; int y; long long z; };
};
person pyon    schedule 11.04.2009
comment
Не е вярно. Обичайна употреба е достъп до по-малки части от по-голям тип. Пример: съюз U { int i; char c[4]; }; може да се използва за предоставяне на (специфичен за изпълнение) достъп до байтове на 4-байтово цяло число. - person ; 11.04.2009
comment
О, вярно... не съм забелязал тази възможност. Винаги съм получавал достъп до части от по-голям тип, използвайки байтови смени и подобни неща. - person pyon; 12.04.2009
comment
@anon - специфична за изпълнението или просто UB, в зависимост от вашия компилатор. Разчитането дори на първото е лоша практика, ако може да се избегне. - person underscore_d; 14.06.2016