Профспілки та набивання шрифтів


79

Я шукав якийсь час, але не можу знайти чіткої відповіді.

Багато людей кажуть, що використання профспілок для набору каламбурів - це невизначена та погана практика. Чому це? Я не бачу жодної причини, чому він може робити щось невизначене, враховуючи те, що пам'ять, в яку ви пишете оригінальну інформацію, не буде просто змінюватись самостійно (якщо це не виходить за рамки стека, але це не проблема об'єднання , це було б поганим дизайном).

Люди цитують суворе правило псевдонімів, але мені здається, це все одно, що казати, що ти не можеш цього робити, бо не можеш цього робити.

Також який сенс об'єднання, якщо не набирати каламбур? Я десь бачив, що вони повинні використовуватися для використання одного місця пам'яті для різної інформації в різний час, але чому б просто не видалити інформацію, перш ніж використовувати її знову?

Підсумовувати:

  1. Чому погано використовувати профспілки для покарання типу?
  2. Який сенс у них, як не в цьому?

Додаткова інформація: Я використовую в основному C ++, але хотів би знати про це і C. Зокрема, я використовую союзи для перетворення між плаваючими кодами та необробленою шістнадцяткою для надсилання через шину CAN.


1
Для одного дуже поширеного використання спілок, подумайте про лексичний аналізатор у компіляторі. Він може повернути пару синтаксичних значень синтаксичному аналізатору, і залежно від маркера значення може, наприклад, бути цілим числом, числом із плаваючою комою, символом або покажчиком на рядок. Як би ви найкраще представили ці різні типи значень в одній структурі? Союз звичайно.
Якийсь програміст, чувак,

1
Я висвітлюю подробиці того, чи дозволено штифтування типу через об’єднання як на C, так і на C ++, у своїй відповіді на питання Чому оптимізація вбиває цю функцію? . В основному, завжди легально в C не зрозуміло, чи це легально в C ++, але на практиці більшість компіляторів підтримують це в C ++.
Шафік Ягмур,

Я мав намір додати відповідь на це деякий час тому, але забув, а потім знову наткнувся на це питання, розглядаючи щось інше, ну я просто додав свою відповідь.
Шафік Ягмур

Відповіді:


51

Щоб повторити ітерацію, набір символів через профспілки цілком чудовий у C (але не в C ++). На відміну від цього, використання вказівок вказівників для цього порушує строгий псевдонім C99 і є проблематичним, оскільки різні типи можуть мати різні вимоги до вирівнювання, і ви можете підняти СИГБУС, якщо зробите це неправильно. З профспілками це ніколи не є проблемою.

Відповідними цитатами зі стандартів С є:

C89, розділ 3.3.2.3 §5:

якщо доступ до члена об'єднання об'єднання відбувається після того, як значення було збережено в іншому члені об'єкта, поведінка визначається реалізацією

C11, розділ 6.5.2.3 §3:

Вираз постфікса, за яким слідує. оператор та ідентифікатор позначає члена структури або об'єкта об'єднання. Значення має значення вказаного члена

з наступною виноскою 95:

Якщо член, що використовується для зчитування вмісту об'єкта об'єднання, є не таким, як член, який останній використовувався для зберігання значення в об'єкті, відповідна частина представлення об'єкта значення переосмислюється як представлення об'єкта в новому типі як описаний у 6.2.6 (процес, який іноді називають `` типовим покаранням ''). Це може бути зображення пастки.

Це повинно бути абсолютно зрозумілим.


Джеймс розгублений, оскільки читається розділ 6.7.2.1 §16 C11

Значення щонайбільше одного з членів можна зберігати в об'єкті об'єднання в будь-який час.

Це здається суперечливим, але це не так: На відміну від C ++, в C немає поняття активного члена, і цілком нормально отримати доступ до одного збереженого значення через вираз несумісного типу.

Див. Також додаток J11 § 1 §1:

Значення байтів, які відповідають членам об'єднання, крім останнього, що зберігається у [, не вказані].

У C99 це колись читалося

Значення члена профспілки, крім останнього, що зберігається в [не вказано]

Це було неправильно. Оскільки додаток не є нормативним, він не оцінив власний ТК і мусив почекати до наступної стандартної редакції, щоб виправити.


Розширення GNU до стандартного C ++ (і до C90) явно дозволяють набирати шрифти за допомогою об'єднань . Інші компілятори, які не підтримують розширення GNU, також можуть підтримувати типову команду об'єднання, але це не є частиною стандарту базової мови.


2
У мене немає під рукою своєї копії C90 для перевірки контексту; Я пам'ятаю з обговорень комітетів, що одним із намірів було те, що формулювання має дозволяти реалізації "налагодження", що затримується, якщо доступ був іншим, ніж останній записаний елемент. (Це, звичайно, було наприкінці 1980-х; ставлення Комітету С могло змінитися з тих пір.) Здається, я пам’ятаю, що це було досягнуто невизначеною поведінкою, але визначена реалізація теж зробить свою справу. (Основна відмінність тут полягає в тому, що реалізація повинна була б задокументувати те, що вона робить.)
Джеймс Канце

4
Виноска не є нормативною, і в контексті явно пояснює, чому комітет цього не визначив. Це не визначає поведінку.
James Kanze,

5
@JamesKanze: Значення має значення вказаного члена . Це нормативна частина, роз’яснена у виносці. Якщо всі байти, що складають об'єктне представлення цього члена, приймають задане значення і не відповідають представленню пастки, член також прийме вказане значення. Не має значення, як ці байти потрапили туди ( memcpyшляхом модифікації через char *, через іншого члена спілки, ...). Ви не зможете переконати мене в протилежному, тому, якщо ви не передумаєте, мабуть, безглуздо продовжувати ...
Крістоф

1
Я пам'ятаю з обговорень комітетів, що одним із намірів було те, що формулювання має дозволяти реалізації "налагодження", які затримувались, якщо доступ був іншим, ніж останній записаний елемент. Це могло бути так у 80-х; коли C99 забороняв набирати шрифти через приведення вказівників, виникла потреба в іншому механізмі; так і є; на жаль, схоже, про це не згадується в обґрунтуванні C99, але цілком імовірно, що саме так і сталося
Крістоф

1
У мене теж складається враження, що позиція комітету С змінилася; з 1990 року я займався стандартизацією C ++ і не стежив за ним так уважно. Однак основне правило все ще застосовується: все, що стандарт не визначає, є невизначеною поведінкою. І це явно відноситься до цієї категорії. Я думаю (але не можу довести), що намір полягає у тому, щоб усі типові покарання мали невизначену поведінку, що визначається реалізацією.
James Kanze

19

Початковою метою об’єднань було заощадити простір, коли ви хочете мати можливість представляти різні типи, що, як ми називаємо варіантний тип, див. Boost.Variant як хороший приклад цього.

Іншим поширеним використанням є використання типу покарання за валідність цього, що обговорюється, але практично більшість компіляторів підтримують це, ми можемо бачити, що gcc підтверджує його підтримку :

Практика читання від іншого члена профспілки, ніж від останнього, написаного до нього (так зване "набивання шрифтів"), є поширеною. Навіть із використанням -fstrict-aliasing дозволяється набирати шрифти за умови доступу до пам'яті через тип об'єднання. Отже, наведений вище код працює належним чином.

зауважте, що навіть при використанні -fstrict-aliasing дозволяється набирати шрифти, що вказує на те, що у процесі є проблема з накладанням псевдонімів.

Паскаль Куок стверджував, що у звіті про дефекти 283 з'ясовано, що це було дозволено у C. Звіт про дефекти 283 додав наступну виноску як пояснення:

Якщо член, який використовується для доступу до вмісту об'єкта об'єднання, не є таким самим, як член, який останній використовувався для зберігання значення в об'єкті, відповідна частина представлення об'єкта значення переосмислюється як представлення об'єкта в новому типі як описаний у 6.2.6 (процес, який іноді називають "покаранням типу"). Це може бути зображення пастки.

у C11 це було б виноскою 95.

Незважаючи на те, що в std-discussionтемі групи поштових повідомлень Type Punning via Union наведено аргумент, це невизначено, що видається обґрунтованим, оскільки DR 283не додало нових нормативних формулювань, лише примітку:

На мою думку, це невизначена семантична трясина у C. Не було досягнуто консенсусу між виконавцями та комітетом C щодо того, які саме випадки визначали поведінку, а які не [...]

У C ++ незрозуміло, визначена поведінка чи ні .

Ця дискусія також охоплює принаймні одну причину, через яку дозволити штампування через профспілку небажано:

[...] Правила стандарту C порушують оптимізацію аналізу псевдонімів на основі типу, яку виконують поточні реалізації.

це порушує деякі оптимізації. Другий аргумент проти цього полягає в тому, що використання memcpy має генерувати ідентичний код, і це не порушує оптимізації та чітко визначеної поведінки, наприклад:

std::int64_t n;
std::memcpy(&n, &d, sizeof d);

замість цього:

union u1
{
  std::int64_t n;
  double d ;
} ;

u1 u ;
u.d = d ;

і ми бачимо, що за допомогою godbolt це генерує ідентичний код, і аргумент робиться, якщо ваш компілятор не генерує ідентичний код, це слід вважати помилкою:

Якщо це справедливо для вашої реалізації, я пропоную вам подати помилку. Порушення реальних оптимізацій (все, що базується на аналізі псевдонімів на основі типу), щоб обійти проблеми з продуктивністю з певним компілятором, мені здається поганою ідеєю.

У дописі в блозі Type Punning, Strict Aliasing та Optimization також приходить до подібного висновку.

Обговорення списку розсилки з невизначеною поведінкою: Набирання шрифтів, щоб уникнути копіювання, охоплює багато тієї ж землі, і ми бачимо, наскільки сірою може бути територія.


1
Твердження про те, що memcpy виробляє ідентичний код, ігнорує той факт, що більш ефективний код може бути сформований, якщо компілятори документують шаблони, де буде розпізнано псевдонім, і в цьому випадку компілятору потрібно буде зробити лише невелику кількість м'яко песимістичних (але, швидше за все, точних) припущення, тоді як memcpy часто змушує компілятор робити більш песимістичні припущення. Сам код для memcpy може виглядати чудово, але його вплив на код навколо нього не так вже й великий.
supercat

Варто зазначити, що з C ++ 17 ми отримуємо std::variantяк варіант
Джастін,

3
Також може бути добре згадати, що std::memcpyце дійсно, лише якщо типи є TriviallyCopyable
Джастін,

@supercat, якщо ви можете навести приклад godbolt, який показує цей ефект, це було б дуже корисно. Як я розумію позицію Річарда, це не повинно бути так, можливо, це помилка.
Шафік Ягмур,

@ShafikYaghmour: Враховуючи код uint16_t *outptr; void store_double_halfword(uint32_t dat) { uint32_t *dp = (uint32_t*)outptr; outptr = dp+1; memcpy(dp, &dat, sizeof (uint32_t)); } void store_loop1(uint32_t *src){ for (int i=0; i<100; i++) store_next_word1(src[i]); }, компілятор не міг би уникнути необхідності перезавантажувати та перезаписувати outptrкожен прохід через цикл, коли код використовує memcpy. Якщо на компілятор можна покластися, щоб розглядати акторський склад uint16_t*як знак того, що функція може отримати доступ до речей типу uint16_tабо uint32_t, таким чином дозволяючи код ...
supercat

6

Це дозволено в C99:

Зі стандарту: 6.5.2.3 Структура та члени профспілки

Якщо член, який використовується для доступу до вмісту об'єкта об'єднання, не є таким самим, як член, який останній використовувався для зберігання значення в об'єкті, відповідна частина представлення об'єкта значення переосмислюється як представлення об'єкта в новому типі як описаний у 6.2.6 (процес, який іноді називають "покаранням типу"). Це може бути зображення пастки.


6
@JamesKanze Чи можете ви розширити інформацію про те, як “відповідна частина представлення об’єкта значення переосмислюється як представлення об’єкта в новому типі, як описано в 6.2.6 (процес, який іноді називають“ типовим покаранням ”). Це може бути уявлення про пастку ”- це вигадливий спосіб сказати, що це невизначена поведінка? Мені здається, там сказано, що прочитане - це переосмислення нового типу, і що це вигадливий спосіб сказати, що це поведінка, визначена реалізацією , якщо що.
Паскаль Куок,

8
@JamesKanze Я вважаю, що "Це може бути уявлення про пастку", що означає, що якщо новий тип має уявлення про перехоплення, то за умов, визначених реалізацією, результат покарання типу може бути одним із них.
Паскаль Куок,

1
@JamesKanze: набивання типу через профспілки є чітко визначеним, якщо це не призводить до представлення пастки (і тип джерела не менший за цільовий тип); це рішення у кожному конкретному випадку залежно від типів та цінностей ; у приміщенні C99 є виноска, з якої дуже чітко видно, що нанесення шрифтів законним; (ненормативний!) додаток перерахував його неправильно як невизначену ( не визначену) поведінку; додаток було зафіксовано C11
Крістоф

1
@JamesKanze: так, це стосується лише C; проте використання профспілок таким чином ніколи не було невизначеною поведінкою; див. чернетку C89, розділ 3.3.2.3: якщо доступ до члена об’єднаного об’єкта здійснюється після того, як значення було збережено в іншому члені об’єкта, поведінка визначається реалізацією
Крістоф,

1
Нарешті: цитований текст є частиною ненормативної примітки; це слід трактувати як одне з можливих виправдань. Відповідний текст міститься в §6.7.2.1 / 16, де чітко зазначено, що одночасно може бути дійсним щонайбільше один елемент союзу. Тож ця відповідь просто неправильна.
James Kanze,

5

Існує (або, принаймні, було ще в C90) дві модифікації для здійснення цієї невизначеної поведінки. Перший з них полягав у тому, що компілятору було дозволено генерувати додатковий код, який відстежував те, що було в об'єднанні, і генерував сигнал, коли ви зверталися до неправильного члена. На практиці я не думаю, що хтось колись це робив (можливо, CenterLine?). Іншим було відкрито можливості оптимізації, які ми використовуємо. Я використовував компілятори, які відкладали запис до останнього можливого моменту на тій підставі, що це може бути не потрібно (оскільки змінна виходить за межі обсягу, або є подальша запис іншого значення). Логічно, можна було б очікувати, що цю оптимізацію буде вимкнено, коли об’єднання буде видно, але це не було в найдавніших версіях Microsoft C.

Питання покарання типу є складними. Комітет С (ще наприкінці 1980-х) більш-менш дотримувався позиції, згідно з якою для цього слід використовувати зліпки (на C ++, reinterpret_cast), а не профспілки, хоча обидва методи були широко поширені на той час. З тих пір деякі компілятори (наприклад, g ++) дотримуються протилежної точки зору, підтримуючи використання профспілок, але не використання литих. А на практиці не працюють ні ті, ні інші, якщо не відразу стає очевидним, що існує набивання шрифтів. Це може бути мотивацією точки зору g ++. Якщо ви звертаєтесь до члена профспілки, одразу стає очевидним, що може бути набір шрифтів. Але звичайно, враховуючи щось на зразок:

int f(const int* pi, double* pd)
{
    int results = *pi;
    *pd = 3.14159;
    return results;
}

зателефонував із:

union U { int i; double d; };
U u;
u.i = 1;
std::cout << f( &u.i, &u.d );

є абсолютно законним згідно з жорсткими правилами стандарту, але не працює з g ++ (і, мабуть, багатьма іншими компіляторами); при компіляції f, компілятор припускає , що pi і pdможе не псевдонім, і сортує запис , щоб *pdі зчитує з *pi. (Я вважаю, що це ніколи не було наміром гарантувати це. Але нинішня редакція стандарту це гарантує.)

РЕДАГУВАТИ:

Оскільки інші відповіді стверджують, що поведінка насправді визначена (в основному заснована на цитуванні ненормативної примітки, вирваної з контексту):

Правильна відповідь тут - pablo1977: стандарт не робить спроб визначити поведінку, коли задіяно типову програмування. Ймовірна причина цього полягає в тому, що не існує портативної поведінки, яку він міг би визначити. Це не заважає конкретній реалізації визначити її; хоча я не пам'ятаю жодних конкретних обговорень проблеми, я майже впевнений, що метою було те, що реалізації щось визначають (і більшість, якщо не всі, так роблять).

Що стосується використання об'єднання для набору шрифтів: коли комітет C розробляв C90 (наприкінці 1980-х), існував чіткий намір дозволити реалізацію налагодження, що проводило додаткову перевірку (наприклад, використання вказівників жиру для перевірки меж). З дискусій того часу було зрозуміло, що намір полягає у тому, що реалізація налагодження може кешувати інформацію про останнє значення, ініціалізоване в об’єднанні, і захоплювати, якщо ви намагалися отримати доступ до чогось іншого. Це чітко зазначено в §6.7.2.1 / 16: "Значення щонайбільше одного з членів може зберігатися в об'єкті об'єднання в будь-який час". Доступ до значення, якого немає, є невизначеною поведінкою; його можна уподібнити до доступу до неініціалізованої змінної. (Тоді були деякі дискусії щодо того, чи є доступ до іншого члена того самого типу законним чи ні. Однак я не знаю, якою була остаточна резолюція; приблизно після 1990 року я перейшов до C ++.)

Що стосується цитати з C89, то сказати, що поведінка визначається реалізацією: знайти її в розділі 3 (Терміни, визначення та символи) видається дуже дивним. Мені доведеться шукати це в моїй копії C90 вдома; той факт, що він був вилучений у пізніших версіях стандартів, свідчить про те, що комітет вважав його наявність помилкою.

Використання спілок, які підтримує стандарт, є засобом для імітації деривації. Ви можете визначити:

struct NodeBase
{
    enum NodeType type;
};

struct InnerNode
{
    enum NodeType type;
    NodeBase* left;
    NodeBase* right;
};

struct ConstantNode
{
    enum NodeType type;
    double value;
};
//  ...

union Node
{
    struct NodeBase base;
    struct InnerNode inner;
    struct ConstantNode constant;
    //  ...
};

і легально отримати доступ до base.type, хоча Node було ініціалізовано через inner. (Той факт, що §6.5.2.3 / 6 починається з "Складається одна спеціальна гарантія ..." і надалі явно допускає це, є дуже вагомим свідченням того, що всі інші випадки мають бути невизначеною поведінкою. І звичайно, є є твердженням, що "невизначена поведінка в іншому випадку позначається в цьому міжнародному стандарті словами" 'невизначена поведінка' 'або пропуском будь-якого явного визначення поведінки "у §4 / 2; з метою аргументувати, що поведінка не визначена , ви повинні показати, де це визначено в стандарті.)

Нарешті, щодо набору шрифтів: усі (або принаймні всі, що я використовував) реалізації певним чином підтримують це. Тоді я склав враження, що намір полягав у тому, щоб кастинг покажчиків був способом, яким його підтримувала реалізація; у стандарті С ++ є навіть (ненормативний) текст, який передбачає, що результати reinterpret_castможуть бути "несподіваними" для когось, хто знайомий з базовою архітектурою. На практиці, однак, більшість реалізацій підтримують використання union для набору тексту, за умови, що доступ здійснюється через члена об'єднання. Більшість реалізацій (але не g ++) також підтримують прив'язку покажчиків за умови, що складання вказівника чітко видно компілятору (для деяких невизначених визначень прив'язки покажчика). А "стандартизація" базового обладнання означає, що такі речі:

int
getExponent( double d )
{
    return ((*(uint64_t*)(&d) >> 52) & 0x7FF) + 1023;
}

насправді досить портативні. (Звичайно, це не буде працювати на мейнфреймах.) Що не працює, - це речі, подібні до мого першого прикладу, де компілятор невидимий. (Я майже впевнений, що це дефект стандарту. Здається, я пам’ятаю, навіть бачивши АД щодо цього.)


3
це було визначено реалізацією , не визначено в C90 - зробити це незаконним - це C ++ - ism
Christoph

4
насправді, комітет С заборонив використовувати приведення покажчиків для набору шрифтів, запровадивши ефективний набір тексту, тому використання профспілок - це спосіб С
Крістоф,

2
@Cristoph Це все ще невизначена поведінка в C11, принаймні в моїй копії. § 6.7.2.1 / 16 це цілком ясно. C ++ ще зрозуміліший, оскільки він має концепцію життя об'єкта, окремої від тривалості зберігання, але навіть у C доступ до неініціалізованого об'єкта (крім послідовності байтів) є невизначеною поведінкою та присвоєнням одному елементу об'єднання робить усіх інших "неініціалізованими".
James Kanze,

Мені дуже шкода, але ви це помилково, наскільки C стурбований; Я написав відповідь спеціально для вас, перерахувавши відповідні цитати
Крістоф,

1
@Christoph Проблема полягає в тому, що ваш аргумент значною мірою залежить від ненормативності, не вирваної з контексту. Значущий текст наведено у §6.7.2.1 / 16. І C справді має поняття недійсного об'єкта, що призводить до невизначеної поведінки під час доступу до нього.
James Kanze

4

КОРОТКИЙ ВІДПОВІДЬ: Введіть покарання покарання може бути безпечним за кількох обставин. З іншого боку, хоча, здається, це дуже відома практика, схоже, що стандарт не надто зацікавлений у тому, щоб зробити її офіційною.

Я буду говорити лише про С (а не С ++).

1. ТИП ПАНІНГУ І СТАНДАРТИ

Як вже вказували люди, але штампування дозволено в стандарті C99, а також C11, у підрозділі 6.5.2.3 . Однак я перепишу факти з власним сприйняттям проблеми:

  • Розділ 6.5 стандартних документів C99 та C11 розробляє тему виразів .
  • Підрозділ 6.5.2 стосується виразів постфікса .
  • Подподраздела 6.5.2.3 говорить про структурах і союзах .
  • У параграфі 6.5.2.3 (3) пояснюється оператор крапок, застосований до об'єкта structабо unionоб'єкта, і яке значення буде отримано.
    Тут же з’являється виноска 95 . Ця примітка говорить:

Якщо член, який використовується для доступу до вмісту об'єкта об'єднання, не є таким самим, як член, який останній використовувався для зберігання значення в об'єкті, відповідна частина представлення об'єкта значення переосмислюється як представлення об'єкта в новому типі як описаний у 6.2.6 (процес, який іноді називають "покаранням типу"). Це може бути зображення пастки.

Той факт, що штампування типів ледь з'являється, і як виноска, це дає підказку, що це не є актуальною проблемою в програмуванні на Сі.
Власне, основною метою використання unionsє економія місця (в пам'яті). Оскільки декілька учасників мають однакову адресу, якщо один знає, що для кожного учасника будуть використовуватися різні частини програми, ніколи одночасно, тоді unionзамість a можна використовувати a structдля економії пам'яті.

  • Підпункт 6.2.6 .
  • У підрозділі 6.2.6 йдеться про те, як представлені об’єкти (скажімо, у пам’яті).

2. ПРЕДСТАВЛЕННЯ ВИДІВ ТА ЇЇ НЕСПРАВНОСТІ

Якщо ви звернете увагу на різні аспекти стандарту, ви можете бути майже ні в чому не впевнені:

  • Представлення покажчиків чітко не визначено.
  • Найгірше, вказівники, що мають різні типи, можуть мати різне представлення (як об'єкти в пам'яті).
  • unionчлени мають однакову адресу заголовка в пам'яті, і це та сама адреса, що й самого unionоб'єкта.
  • struct члени мають збільшення відносної адреси, починаючи з точно тієї ж адреси пам'яті, що і у struct об'єкт. Однак байти заповнення можуть бути додані в кінці кожного члена. Скільки? Це непередбачувано. Байти для заповнення використовуються головним чином для вирівнювання пам'яті.
  • Арифметичні типи (цілі числа, дійсні та комплексні числа з плаваючою комою) можуть бути представлені різними способами. Це залежить від реалізації.
  • Зокрема, цілочисельні типи можуть мати біти заповнення . Я вважаю, що це неправда для настільних комп’ютерів. Однак стандарт залишив двері відкритими для такої можливості. Біти для заповнення використовуються для приватних цілей (парність, сигнали, хто знає), а не для утримання математичних значень.
  • signed типи можуть мати 3 способи представлення: доповнення 1, доповнення 2, просто розрядний знак.
  • Ці charтипи займають лише один байт, а один байт може мати кілька бітів різних 8 (але не менше 8).
  • Однак ми можемо бути впевнені в деяких деталях:

    a. Ці charтипи не біти заповнення.
    b. Ці unsignedцілі типи представлені так само , як в двійковій формі.
    c. unsigned charзаймає рівно 1 байт, без заповнення бітів, і немає жодного представлення пастки, оскільки всі біти використовуються. Більше того, воно представляє значення без будь-якої двозначності, слідуючи двійковому формату для цілих чисел.

3. ТИП-ПАНІНГ проти ТИП-ПРЕДСТАВЛЕННЯ

Усі ці спостереження показують, що, якщо ми спробуємо виконати типову команду з unionчленами, що мають різні типи unsigned char, ми могли б мати багато неоднозначностей. Це не портативний код, і, зокрема, ми могли б мати незрозумілу поведінку нашої програми.
Однак стандарт дозволяє такий вид доступу .

Навіть якщо ми впевнені у конкретному способі, що кожен тип представлений у нашій реалізації, ми могли б мати послідовність бітів, що взагалі нічого не означає в інших типах ( представлення пастки ). У цьому випадку ми нічого не можемо зробити.

4. БЕЗПЕЧНИЙ ВИПАДК: непідписаний знак

Єдиним безпечним способом використання штампування типів є масиви unsigned charабо колодязі unsigned char(оскільки ми знаємо, що члени об’єктів масиву суворо суміжні, і немає байтів для заповнення, коли їх розмір обчислюється sizeof()).

  union {
     TYPE data;
     unsigned char type_punning[sizeof(TYPE)];
  } xx;  

Оскільки ми знаємо, що unsigned charвоно представлене у суворій двійковій формі, без заповнення бітів, тут можна використовувати тип punning, щоб поглянути на двійкове представлення члена data.
Цей інструмент може бути використаний для аналізу представлення значень даного типу в конкретній реалізації.

Я не бачу іншого безпечного та корисного застосування штампування згідно стандартних специфікацій.

5. КОМЕНТАР ПРО АКІДИ ...

Якщо ви хочете пограти з типами, краще визначте власні функції трансформації, або просто використовуйте закиди . Ми можемо згадати цей простий приклад:

  union {
     unsigned char x;  
     double t;
  } uu;

  bool result;

  uu.x = 7;
  (uu.t == 7.0)? result = true: result = false;
  // You can bet that result == false

  uu.t = (double)(uu.x);
  (uu.t == 7.0)? result = true: result = false;
  // result == true

Я не бачив жодного посилання зі «Стандарту», ​​що він робить виняток для штампування через char, і тому я дуже скептичний. У вас є? Зауважте, що це цілком може бути визначено інакше, ніж суворе накладання псевдонімів, яке робить виняток для charтипів. Нам би добре не поєднати ці два.
underscore_d

1
@underscore_d: Немає такого явного посилання на типи символів у наборі шрифтів. Я дійшов висновку, зібравши факти: я можу прочитати в Стандарті C11, що (1) набивання типів є допустимою операцією в C через членів профспілки, (2) хоча проблеми можуть виникати через представлення пастки , (3) але типи символів не мають подання, (4) кожен тип символів займає рівно 1 байт. Отже, масиви символьного типу можна використовувати для «зчитування» байтів будь-якого іншого об’єкта в об’єднаному члені. Однак існує невизначена поведінка при доступі до членів атомних союзів (або структур також).
pablo1977

Знаєте, я думаю, що я просто пропустив той момент, коли ви сказали, що будете говорити лише про С. Вибачте. Очевидно, коли я маю на меті дослідити C ++, це все, що я бачу, навіть коли це не тема! Мені подобаються ваші міркування щодо C, але мені доведеться припустити, що в C ++, який не допускає покарання, це UB для каламбуру через char(але не для псевдоніма через покажчик). Я відчуваю, що вони повинні бути безпосередньо пов'язані, але я не можу знайти джерело C ++, яке говорить "так, роби все, що хочеш, charу union. але я зупиню ОТ на вашій відповіді зараз :)
underscore_d
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.