Чи включають будь-які помітні розширення C цілі типи, поведінка яких не залежить від розміру машинного слова


12

Цікавою характеристикою C порівняно з іншими мовами є те, що багато типів даних базуються на розмірі слова цільової архітектури, а не в конкретних значеннях. Хоча це дозволяє використовувати мову для запису коду на машинах, які можуть мати труднощі з певними типами, це дуже важко розробити код, який буде працювати послідовно в різних архітектурах. Розглянемо код:

uint16_t ffff16 = 0xFFFF;
int64_t who_knows = ffff16 * ffff16;

У архітектурі, де int16 біт (все ще стосується багатьох малих мікроконтролерів), цей код призначив би значення 1, використовуючи чітко визначену поведінку. На машинах із int64 бітами він присвоїв би значення 4294836225, знову ж таки використовуючи чітко визначену поведінку. На машинах із int32 бітами, ймовірно, присвоїть значення -131071 (я не знаю, чи було б це визначено впровадженням або не визначеною поведінкою). Незважаючи на те, що код не використовує нічого, окрім того, що номінально повинні бути типи "фіксованого розміру", стандарт вимагає, щоб два різних види компілятора, які використовуються сьогодні, дали два різні результати, і багато популярних сьогодні компіляторів дадуть третину.

Цей конкретний приклад дещо надуманий, оскільки я б не очікував, що в реальному коді присвоюється добутковість двох 16-бітних значень безпосередньо 64-бітовому значенню, але він був обраний як короткий приклад для показу трьох цілих способів рекламні акції можуть взаємодіяти з типовими форматами, які не мають фіксованого розміру. Існують деякі ситуації в реальному світі, коли математика для непідписаних типів повинна виконуватися за правилами математичної цілочисельної арифметики, інші, коли це потрібно, щоб вона виконувалася за правилами модульної арифметики, а деякі там, де це дійсно немає ' t матерія. Дуже багато реального коду для таких речей, як контрольні uint32_tсумки, спираються на арифметичне обгортання моди 2³² та на можливість виконувати довільнуuint16_t арифметичні та отримують результати, які є, як мінімум, визначаються як точні мод 65536 (на відміну від спровокування не визначеної поведінки).

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

  1. Додаючи директиву, яка б доручала компілятору примушувати певні "основні" цілі типи бути певних розмірів.

  2. Додаючи директиву, яка б доручала компілятору оцінювати різні сценарії просування так, ніби типи машини мали певні розміри, незалежно від фактичних розмірів типів у цільовій архітектурі.

  3. Дозволяючи засоби декларування типів із специфічними характеристиками (наприклад, заявляють, що тип повинен вести себе як мод-65536 обертаюче алгебраїчне кільце, незалежно від розміру основного слова, і не повинен неявно перетворюватися на інші типи; додаючи wrap32до а, intслід отримати Результат типу wrap32незалежно від того, чи intбільший він за 16 біт, при цьому додавання wrap32безпосередньо до атрибуту wrap16має бути незаконним (оскільки жоден не може перетворити на інший)

Моє власне уподобання було б третьою альтернативою, оскільки це дозволило б навіть машинам з незвичними розмірами слів працювати з великою кількістю коду, який очікує, що змінні "загортаються" так само, як і з двома розмірами; компілятору, можливо, доведеться додати інструкції з бітового маскування, щоб змусити тип вести себе належним чином, але якщо коду потрібен тип, який завершує мод 65536, краще, щоб компілятор генерував таку маскування на машинах, які потребують цього, ніж захаращувати вихідний код або просто мати такий код, непридатний для використання на машинах, де таке маскування буде потрібно. Мені цікаво, чи існують якісь поширені розширення, які б досягли портативної поведінки за допомогою будь-якого з перерахованих вище засобів, або через якісь засоби, про які я не думав.

Щоб уточнити, що я шукаю, є кілька речей; дуже помітно:

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

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

Я жодним чином не бажаю, щоб мене розглядали як зневагу до Комітету зі стандартів C чи роботи, яку вони виробили; Однак я очікую, що протягом декількох років стане необхідним змусити код працювати правильно на машинах, де "природний" тип просування складе 32 біта, а також на тих, де це буде 64 біт. Я думаю, що при деяких скромних розширеннях до мови (скромніших, ніж у багатьох інших змінах між C99 і C14) можна було б не тільки забезпечити чіткий спосіб ефективного використання 64-бітових архітектур, але і в угоді також полегшити взаємодію з машини "незвичайного розміру слова", стандарт яких історично схилявся назад, щоб підтримати [наприклад, дозволяючи машинам з 12-бітовим charкодом запускати код, який очікуєuint32_tобернути мод 2³²]. Залежно від напрямку майбутнього розширення, я б також очікував, що має бути можливість визначити макроси, які дозволять коду, написаному сьогодні, бути корисним для сьогоднішніх компіляторів, де типи цілих чисел за замовчуванням поводяться як "очікувані", але також можуть бути використані для майбутніх компіляторів, де ціле число типи за замовчуванням поводяться по-різному, але де можна надати необхідну поведінку.


4
@RobertHarvey Ви впевнені? Як я розумію, ціле просування , якщо intвоно більше, ніж uint16_tоперанди множення будуть просунуті, intа множення буде здійснено як intмноження, і отримане intзначення буде перетворене int64_tв ініціалізацію who_knows.

3
@RobertHarvey Як? У коді ОП про це не згадується int, але він все одно прокрадається. (Знову припускаю, що моє розуміння стандарту С є правильним.)

2
@RobertHarvey Звичайно, це звучить погано, але якщо ви не можете вказати на такий спосіб, ви нічого не робите, говорячи "так, ви повинні робити щось не так". Саме питання полягає в тому, як уникнути цілої промоції або обійти її ефекти!

3
@RobertHarvey: Одна з історичних цілей Комітету з C стандартів в тому , щоб зробити можливим практично для будь-якої машини , щоб мати «C компілятор», і є правила , бути досить конкретної , що незалежно розвинені компілятори для якої - небудь конкретної цільової машини буде бути переважно взаємозамінними. Це було ускладнено тим, що люди почали писати компілятори C для багатьох машин ще до того, як були складені стандарти, а Комітет зі стандартів не хотів забороняти компіляторам робити щось, на що може покладатися існуючий код . Деякі досить фундаментальні аспекти стандарту ...
supercat

3
... вони не є тому, що ніхто не намагався сформулювати набір правил, які "мали сенс", а скоріше тому, що Комітет намагався знищити все те, що вже існувало незалежними упорядниками, які вже існували . На жаль, такий підхід призвів до стандартів, які одночасно занадто розпливчасті, щоб дозволити програмістам вказувати, що потрібно робити, але занадто специфічним, щоб дозволити компіляторам "просто робити це".
supercat

Відповіді:


4

Як типовий намір такого коду

uint16_t ffff16 = 0xFFFF;
int64_t who_knows = ffff16 * ffff16;

полягає в тому, щоб виконати множення на 64 біти (розмір змінної, в яку зберігається результат), звичайним способом отримання правильного результату (незалежного від платформи) є передача одного з операндів, щоб примусити 64-бітове множення:

uint16_t ffff16 = 0xFFFF;
int64_t i_know = (int64_t)ffff16 * ffff16;

Я ніколи не стикався з розширеннями на C, які роблять цей процес автоматичним.


1
Моє запитання полягало не в тому, як примусити правильну оцінку одного конкретного арифметичного виразу (залежно від того, якого результату ви хочете, або відкиньте операнда, uint32_tабо використовуйте макрос, визначений як #define UMUL1616to16(x,y)((uint16_t)((uint16_t)(x)*(uint16_t)(y)))або #define UMUL1616to16(x,y)((uint16_t)((uint32_t)(x)*(uint16_t)(y)))залежно від розміру int), а скоріше, чи є будь-які нові стандарти, як керувати такими речами корисно, а не визначати власні макроси.
supercat

Я також мав би зазначити, що для таких речей, як хешування і розрахунки контрольної суми, метою часто буде отримання результату і прив'язка його до розміру операндів. Типовим наміром такого виразу (ushort1*ushort2) & 65535uбуло б виконати арифметику mod-65536 для всіх значень операнда. Читаючи обґрунтування C89, я вважаю, що цілком зрозуміло, що, хоча автори визнавали, що такий код може виявитися невдалим у деяких реалізаціях, якщо результат перевищить 2147483647, вони очікували, що такі реалізації ставатимуть все рідше. Такий код часом не вдається на сучасних gcc.
supercat
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.