Розширення мов С та С++. Частина 1

стаття (і я сподіваюся що серія статей) присвячено нестандартним розширенням мов С та С++, які існують практично в кожному компіляторі.
Мовні розширення — це додаткові можливості і фічі мови, що не входять в стандарт, але тим ні менш підтримувані компіляторами. Досліджувати ці розширення дуже цікаво — в першу чергу тому, що вони виникли не на порожньому місці; кожне розширення — результат нагальної потреби, яка виникає у великої кількості програмістів. А мені цікаво подвійно — оскільки мені подобаються мови програмування і я розробляю свій, часто виявляється, що багато мої ідеї реалізовані саме у розширення мови. Стандарти мов С та С++ розвиваються вкрай повільно, і часом, читаючи опис розширень, так і хочеться вигукнути: «ну це ж очевидно! чому цього досі немає в стандарті?».

Мовні розширення — це така «сіра», тіньова область, про яку зазвичай мало пишуть і мало знають. Але саме цим вона і цікава!

Попередньо можу сказати, що будуть розглянуті компілятори загального призначення gcc, msvs, clang, intel, embarcadero, компілятори для мікроконтролерів iar і keil, і по можливості багато інші компілятори. Найбільше розширень в GCC, що не дивно — вільна розробка сприяє втіленню різних мовних особливостей. До того ж, інформація з розширенням GCC вся зібрана в одному місці, а інформацію по іншим компиляторам доведеться збирати по крупицях. Тому почнемо з GCC.

Розширення мови Сі

Керуючі оператори і блоки коду як вираження

очевиднейшая ідея, широко застосовувана в сучасних гібридних (імперативно-функціональних) мовами. Блок коду може бути значенням у виразі. Значенням вважається значення останнього виразу цього блоку коду.
int q = 100 + ({ int y = foo (); int z;
if (y > 0) z = y;
else z = - y;
z; });


Локальні мітки

Мітки, використовувані для оператора goto, мають область видимості обмежену функцією. Іноді, наприклад при розкритті макросів — це небезпечно, і доцільно обмежити область видимості мітки поточним блоком коду. Такі мітки вимагають попереднього оголошення з використанням ключового слова __label__. Сама мітка оголошується звичайним чином, але тепер її область видимості — блок, а не функція.

Мітки значення

Інша цікава і потужна низькорівнева можливість, пов'язана з оператором goto — використання міток як значень. Фактично ця можливість існує також тільки в Асемблері, де мітка — лише адресу в коді. У GCC однак від спеціального меточного типу відмовилися, а для приведення мітки до типу void* навіщо-то ввели унарний оператор &&. Виглядає дуже красиво і по-хакерски:
static void *array[] = { &&foo &&bar, &&hack };
goto *array[i];

Треба сказати, що з подачі Дейкстри оператор goto перебуває в немилості у більшості програмістів. У багатьох випадках це дійсно виправдано, але не варто забувати, що Сі — хакерський мову, а значить в ньому діє ідеологія переваги можливостей обмежень. І якщо в якомусь специфічному місці, наприклад в ядрі операційної системи, потрібно goto, краще використовувати його, ніж городити асемблерні вставки. А способів зіпсувати код, або зробити його нечитабельним безліч, серед яких goto далеко не на першому місці.

Вкладені функції

Лямбда-функції C++ з'явилися тільки в C++11. А між тим ще в Турбо Паскалі була можливість вкладати одні функції в інші. З появою С++ і класів нічого не змінилося — класи можна було вкладати у функції й інші класи, але функції у функції вкладати було раніше, не можна. GCC виправляє цю прикру асиметрію в мові.
Вкладені функції підтримують доступ до змінних обіймають, але на відміну від лямбд С++ не вимагають явного вказівки «замикання», і на відміну від лямбд з високорівневих мов, не організовують такі «замикання» автоматично. Ще одна цікава можливість — goto з вкладеної функції містку. Це більше схоже на прообраз кидання винятку.

Перенаправлення виклику зі змінним числом аргументів на іншу функцію

Спеціальні мовні конструкції, призначені для передачі змінного числа аргументів функції в іншу функцію зі змінним числом аргументів, при цьому інформація про кількість аргументів не потрібно. Як відомо, стандартним способом роботи зі змінним числом аргументів в Сі є макроси va_start(), va_arg(), va_end() і тип va_list. Спосіб заснований на тому, що аргументи функцій в Сі записуються в стек в зворотному порядку, а ці макроси просто надають доступ до пам'яті стека. Але в даному розширенні ми явно бачимо щось новеньке. Що ж це?
void * __builtin_apply_args () — функція виділяє пам'ять на стеку і копіює туди аргументи викликає функції.
void * __builtin_apply (void (*function)(), void *arguments, size_t size) — функція приймає блок даних, створений за допомогою __builtin_apply_args, вказівник на функцію і розмір стека для неї; всередині формується виклик функції з переданими аргументами. Повертає блок даних на стеку, в якому зберігається повернуте значення, повернуте із function.
void __builtin_return (void *result) — функція замінює звичайний return (тобто після цього buildin'а код не виконується) і повертає результат виконання function, запакований у result.
Таким чином, механізм докорінно відрізняється від va_list і може бути застосований тоді, коли існує функція зі змінним числом аргументів, не має v-версії (тобто версією, що приймає va_list — типу vprintf).
З деяких пір з'явилися ще два builtin'a, використовуваних тільки в inline-функції, які жорстко инлайнятся завжди (а не на усмортрение компілятора, як це буває зі звичайними inline-функиями).
__builtin_va_arg_pack () представляє весь список неименованных аргументів; цей builtin підставляють безпосередньо замість списку аргументів змінної довжини.
__builtin_va_arg_pack_len () повертає кількість неименованных аргументів.

Як можна здогадатися з вимог обов'язковості inline, ці builtin'и працюють на етапі компіляції, ніяких маніпуляцій зі стеком і т. п. в рантайме не проводиться.

Оператор typeof

Оператор на етапі компіляції повертає тип виразу. Аналогічний оператор decltype з'явився таки в С++ не так давно. Однак нагадаю, що зараз ми розглядаємо розширення СІ, а не С++! (хоча вони звичайно ж доступні і в gcc c++)

Скорочений умовний оператор

Вираз
x ? x : y

може бути скорочено до
x ? : y

Це зручна форма запису, особливо якщо x сам по собі — довге вираження. До речі, називається така форма Elvis operator і вона відрізняється від Null coalescing_operator (існуючого наприклад в C#) тим, що Elvis operator призводить перший операнд до типу bool і порівнює з false, а Null coalescing порівнює операнд строго зі спеціальним значенням null.

Типи __int128 і long long

Ще одне очевидне для розширення 128-бітних і 64-бітних цілих чисел. Тип long long стандартизований як З так і в З++, 128-бітних чисел стандарту поки немає. Цікаво, якщо буде, то як він буде називатися? long long long і unsigned long long long?

complex

Підтримка комплексних чисел будь-якого типу на рівні мови. Не впевнений що такі типи має сенс вводити в мову, але нагадаю — це сі, тут немає нативних об'єктів, конструкторів, шаблонів та іншого (а по суті це шаблонний тип). У мові введена підтримка суфіксів 'i', 'j' (це одне і те ж), операторів __real__ і __imag__, а також набору допоміжних функцій.
Досить глибока мовна підтримка дозволяє замислитися, а що повинно бути в мові для того щоб можна було комфортно реалізовувати і користуватися такими спеціальними типами без вбудовування безпосередньо компілятор.

floating types, half precision

Додаткові типи з плаваючою точкою: __float80, __float128, __fp16.
У самому справі, якщо відкрити стандарт IEEE 754, то виявиться що типів дещо більше, ніж всім відомі float і double (і long double, якщо хто ще пам'ятає).

Decimal float

Ще один цікавий формат чисел з плаваючою точкою — за основою 10, а не 2 (дивимося посилання вище, ці формати там теж є). Нагадаю, що класичний float і double в деяких випадках дають забавні похибки, пов'язані з тим, що внутрішнє підставу ступеня дорівнює 2, а текстовий запис чисел — десяткова (тобто за основою 10). Наприклад, 0.1 + 0.2 != 0.3
Числа з плаваючою точкою за основою 10 застосовуються у фінансових розрахунках, де такі помилки не повинні накопичуватися і приводити до витоків грошей.

Hex floats

Це спосіб запису шістнадцяткових чисел з плаваючою точкою (також пов'язаний з тим, що з допомогою десятковій нотації немає можливості записати деякі числа точно). Замість літери 'e', зайнятої для шістнадцятковій цифри, для експоненційної запису застосовується літера 'p'. Як вам наприклад таке число: 0x12c0a34.f09de78p3? По-моєму, дуже навіть по-хакерски.

Fixed point

Числа з фіксованою точкою — ще одне корисне розширення в GCC. На деяких платформах може не бути FPU, іноді розрахунки з фіксованою крапкою можуть бути швидше і зручніше. На низькому рівні це звичайні цілі числа, для яких приймається ціна розрядів, відмінна від загальноприйнятої. Теоретично, можна було б вирішити будь-яке співвідношення цілої і дробової частин, але в GCC прийняті деякі конкретні співвідношення для основних розмірів слів (2, 4 і 8 байт), реалізовані в модифікатори типів _Fract та _Accum. До того ж, ця можливість чомусь не включена у всі компілятори, так що перевірити цю фічу на практиці мені не вдалося.
Ще один модифікатор _Sat застосовується для обчислень з насиченням — це особливий режим обробки переповнення, при якому якщо результат обчислень не влазить в діапазон даного типу, то в змінну зберігається максимальне або мінімальне значення, можливе для даного типу. Точність втрачається, але зате не виникає переходів через знак, що може бути краще в деяких випадках (обробка кольору, звуку і т. п.)

Іменовані адресні простору

Дуже корисна річ для архітектур з кількома адресними просторами. Наприклад для різних мікроконтролерів. Там є оперативка, flash eeprom, все це за кілька банків. І незалежні систем адресації для кожного адресного простору.

Масиви нульової довжини

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

Порожні структури

На відміну від С++, де такі структури можна офіційно, в Сі це розширення. І в Сі їх розмір (sizeof) дійсно дорівнює нулю, на відміну від С++ де це чомусь 1 байт.

Масиви, розмір яких визначається під час виконання

Очевидна річ. Є функція alloca() яка виділяє пам'ять на стеку; її не потрібно звільняти. GCC додає можливість оголошувати таким чином масиви на рівні мови:
void foo(int n) {
int arr[n];
}

Більш того, GCC дозволяє оголошувати вкладені структури з полями-масиви змінної довжини!
void foo (int n) {
struct S { int x[n]; };
}

І також функції з масивами змінної довжини (де довжина вказується в списку аргументів функції):
void foo(int len, char data[len][len]) {
/* ... */
}

А якщо вам хочеться вказати довжину після масиву, то і це можна! GCC вводить спеціальний синтаксис попереднього оголошення змінної у списку аргументів функції, до речі вкрай цікавий для багатьох інших застосувань (але це вже окрема тема):
void foo (int len; char data[len][len], int len) {
/* ... */
}


Макроси зі змінним числом аргументів

Такі макроси з'явилися в стандарті C99 і C++11. У GCC вони з'явилися раніше. Також підтримуються деякі поліпшення по відношенню до стандартної версії. По суті, макрос зі змінним числом параметрів — це синтаксис, що дозволяє передавати в макрос змінне число аргументів і використовувати пакет цих аргументів як єдине ціле для передачі в інші мовні сутності, що підтримують змінне число аргументів функції, інші макроси а в С++ ще й шаблони). У декларації макросу пакет аргументів позначається як три точки "...", а в тілі — як ідентифікатор __VA_ARGS__.
Тепер про розширення. Перше — замість трьох крапок і __VA_ARGS__ можна використовувати нормальні імена, які декларуються з трьома крапками, а використовуються без них. Це покращує читаність коду, і взагалі дуже гарна ідея сама по собі.
#define LOG(args...) fprintf (stderr, args)

Друге — правильна робота з «завершальними комами». При будь кодогенерации (а макроси це теж кодогенерация) неминуче виникають ситуації, коли в кінці списку будь-яких об'єктів виявляється кома. По розуму, мов програмування слід вважати цю ситуацію нормальною, але на жаль більшість мов (і Сі в тому числі) розглядають це як помилку. Тому придумали милиці — спеціальний синтаксис ##__VA_ARGS__, який прибирає кому в тому випадку, якщо пакет аргументів порожній.

Полегшені правила для перенесення рядків у препроцессоре

Препроцесор сам по собі — річ дуже негарна і небезпечна (про що я регулярно згадую в коментарях до різних статей). але якщо він є — то цілком логічно полегшити деякі суворі вимоги. Зокрема, препроцесор в Сі для реалізації багаторядкових макросів використовує досить дивний і безглуздий синтаксис з бэкслешами. Дане розширення дозволяє наявність пробільних символів після бэкслешей (символи-то невидимі, легко в процесі редагування коду їх туди випадково ввести і не помітити).

Індексація не-lvalue масивів

Зараз це здається очевидним, але в С90 чомусь не можна було індексувати не-lvalue масиви. На щастя, і в С99, і в С++ це можливо.

Арифметика з покажчиками void* і вказівники на функції

Можна арифметичні операції над такими покажчиками. Розмір адресуються об'єктів приймається рівним 1 байту (але з цього випливає дивне наслідок: sizeof(void) і sizeof від функціонального типу дорівнюють 1… що не є добре).

Покажчики на масиви з кваліфікаторів

Тонкощі і відмінності від стандарту реалізації роботи з покажчиками на масиви з кваліфікаторів (const та іншими) в GCC C.

Не константные инициализаторы

Очевидна річ, але за стандартом не можна використовувати в списках ініціалізації (у фігурних дужках) неконстантние об'єкти. Дане розширення відкриває таку можливість:
foo (float f, float g) {
float beat_freqs[2] = { f-g, f+g };
/* ... */
}


Складові літерали

Ще одна очевиднейшая річ, до якої все наближаються з різних сторін, але ніяк не можуть реалізувати остаточно, безповоротно і правильно (що найголовніше). Складові літерали, які можна використовувати в якості об'єктів масивів, функцій і об'єднань — не тільки для ініціалізації, але і просто в коді — для присвоювання, передачі в якості аргументів функцій.
obj = ((struct foo) {x + y, 'a', 0});
char **tbl = (char *[]) { "x", "y", "z" };

Для таких літералів створюються тимчасові об'єкти відповідного типу, які і беруть участь у виразах; тому можливо наприклад таке (здавалося б неможливе, оскільки константа не lvalue)
int i = ++(int) { 1 };


Позначені (designated) елементи в списках ініціалізації

І ще одне красиве розширення списків ініціалізації — всередині списків можна вказувати не тільки всі елементи поспіль, але і конкретні елементи, використовуючи синтаксис десигнаторов. Для масивів це унарні квадратні дужки, в яких вказується індекс елемента. Так.
int a[6] = { [4] = 29, [2] = 15 };

еквівалентно
int a[6] = { 0, 0, 15, 0, 29, 0 };

Можна використовувати діапазони:
int widths[] = { [0 ... 9] = 1, [10 ... 99] = 2, [100] = 3 };

Для структур використовується аналогічний синтаксис з унарной точкою:
struct point p = { .y = yvalue, .x = xvalue };

Можна змішувати обидва типи десигнаторов, а також в одному списку ініціалізації використовувати як десигнаторы так і просто елементи:
struct point ptarray[10] = { [2].y = yv2, {33,44}, [2].x = xv2, [0].x = xv0 };

До речі, це розширення не реалізовано в C++ і його так і не протягли в стандарт. А шкода, це одне з найкрасивіших розширень, і одна з речей, які тепер є в Сі і немає в С++.

Діапазони case

Можливість використовувати діапазони (з трикрапкою) в операторі switch в якості аргументів case:
switch© {
case 'A' ... 'Z': /* ... */ break;
}

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

Приведення до типу об'єднання будь-якого об'єкта, який є членом об'єднання

Якщо є об'єднання
union foo { int i; double d; };

то можна здійснювати явне приведення типу об'єктів int і double до типу foo:
union foo u;
int x;
double y;
u = (union foo) x;
u = (union foo) y;

Аналогічно при передачі аргументів у функцію:
void hack (union foo);
hack ((union foo) x);


Змішування оголошення змінних і коду

Привычнейшая в С++ річ у C90 виявляється теж була розширенням (в С99 її включили в стандарт).

Атрибути функцій, змінних, типів, міток, перерахувань, керуючих операторів

Спеціальне ключове слово __attribute__, що дозволяє призначати певні компілятором атрибути (метаінформацію) різних мовних конструкцій. Після ключового слова в круглих дужках вказується ім'я атрибута. Атрибути можуть бути самі різні. Деякі атрибути загальні, інші специфічні для конкретної архітектури. Атрибути можуть мати аргументи, які вказуються у круглих дужках після імені атрибута. Ось деякі атрибути (насправді їх дуже багато і, можливо, ця тема варта окремої статті)
Атрибути функцій:
noreturn, — фукнцію ніколи не повертає керування,
pure — функція без побічних ефектів (значення залежить тільки від аргументів),
format — має аргументи в стилі форматного рядка printf;
Атрибути тегів:
unused — мітка не використовується для переходу з допомогою goto.
hot — перехід по мітці має високу ймовірність
cold — перехід по мітці має низьку ймовірність
Атрибути елементів перерахування
deprecated — елемент не рекомендується до використання, при його використанні в коді буде попередження
Атрибути операторів
fallthrough — використовується в операторі switch/case і ставиться замість break, щоб вказати компілятору що тут навмисно немає break.
Атрибути змінних
aligned (N) — вказується

Оголошення прототипів спільно з визначеннями функцій в старому стилі

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

Коментарі у стилі C++

Ви не повірите, але взагалі кажучи це теж розширення — залишився з тих давніх часів, коли однорядкові коментарі ще не входили в стандарт Сі. Так, були і такі часи.

Символ долара в ідентифікаторах

Не знаю чи має це сенс (операторних символів і так мало), але ось так — в ідентифікаторах разом з літерами, цифрами і символом підкреслення можна використовувати і символ долара.

Символ Escape

Використання '\e' в строкових або символьних литералах для вставки символу. Хоча символу немає в переносимом наборі символів, але судячи з усього він потрібно частіше ніж інші керуючі символи, не перераховані в POSIX.

Запит вирівнювання полів типу або змінної

Ключове слово __alignof__ повертає вирівнювання, необхідну для поля в певному типі або просто для деякого типу.
Вирівнювання 1 — за межі байта (саме мінімально можливе), 2 — за межі слова, 4 — за кордону подвійного слова і т. д.

inline-функйции

Це всім відома можливість З++, перенесена в Сі.

Використання volatile

Деякі особливості використання volatile в GCC
З цікавого — якщо в коді зустрічається таке
volatile int *ptr;
/*...*/
*ptr;

то GCC інтерпретує це як читання з пам'яті, на яку вказує ptr, і генерує відповідний код

Використання ассемблерних вставок

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

Альтернативні ключові слова для заголовкових файлів

Якийсь черговий милицю сумісності з різними стандартами. Слова __const__, __asm__, і т. д.

Незавершені перерахування

Можна оголосити enum без елементів; оголошувати змінні такого типу не можна, але можна оголошувати покажчики на такі перерахування. Зроблено для можливості попереднього оголошення імені перерахування, за аналогією зі структурами і об'єднаннями.

Імена функцій у вигляді рядків

Це зачатки рефлексії в Сі. Ідентифікатори __FUNCTION__ або __func__) і __PRETTY_FUNCTION__ містять ім'я тієї функції, в якій вони використовуються. __PRETTY_FUNCTION__ містить розширене ім'я — сигнатуру.

Отримання адрес викликають функцій і стекових фреймів

Реалізація доступу до стеку викликів функцій. Спеціальні вбудовані функції (built-in'и) використовуються для отримання адрес повернення потрібного рівня (викликає функції, функції викликала викликає тощо), а також адреси стекових кадрів функції і всіх викликають фунцкий.

Розширення для векторних інструкцій

Багато процесори підтримують векторні типи даних та інструкції (наприклад SIMD — single instruction, multiple data).
Дане розширення підтримує оголошення векторних типів (з допомогою атрибута vector_size) і далі використання звичайних операцій над цими типами. Типи по суті є масивів, але операції над ним подібні операціям над звичайними змінними. Тим ні менш доступна індексація як в масиві.
typedef int v4si __attribute__ ((vector_size (16)));
v4si a = {1,2,3,4};
v4si b = {3,2,1,4};
v4si c;
a = b + 1; // a = b + {1,1,1,1};
a = 2 * b; // a = {2,2,2,2} * b;
c = a > b; // c = {0, 0,-1, 0}
c = a == b; // c = {0,-1, 0,-1}

Також доступна спеціальна вбудована функція __builtin_shuffle для перестановки елементів у векторі або формуванні одного вектора з двох згідно індексного масці:
v4si a = {1,2,3,4};
v4si b = {5,6,7,8};
v4si mask1 = {0,1,1,3};
v4si mask2 = {0,4,2,5};
v4si res;
res = __builtin_shuffle (a, mask1); /* res is {1,2,2,4} */
res = __builtin_shuffle (a, b, mask2); /* res is {1,5,3,6} */


Спеціальний синтаксис для offsetof

Оператор offsetof, що повертає зміщення поля в байтах від початку структури, може бути реалізований макросом виду
offsetof(s, m) (size_t)&(((s *)0)-m)

Однак, це є невизначеним поведінкою за стандартом Сі (за розіменування нуля) і також призводить до різних небажаних попереджень компілятора; тому для реалізації offsetof використовується вбудована функція __builtin_offsetof

Різні вбудовані функції (builtins)

Це поняття рідко виділяється в самостійну сутність — а даремно. Вбудовані функції займають проміжне місце між ключовими словами мови і звичайними функціями і використовуються повсюдно, і більшість програмістів навіть не замислюється про їх природу.
Наприклад синус sin(). Візуально це функція, веде себе як функція (можна навіть взяти адреса) Однак на рівні компілятора ця конструкція перетворюється в одну або кілька ассемблерних команд FPU, а ніяк не на виклик функції (за винятком випадків емуляції FPU для тих архітектур, які не підтримують плаваючу точку). Такі функції називаються вбудованими (builtin) і генеруються безпосередньо компілятором, що дозволяє реалізувати функціональність, недоступну для бібліотечних функцій. Сюди відносяться функції для атомарного доступу до пам'яті, перевірки арифметичних переповнення, розширення Cilk Plus, математиченские функції, безліч функцій для роботи з конкретними платформами і процесорами і т.д.

Прагми

Прагми — директиви, призначені в загальному випадку для тонкого управління процесом компіляції безпосередньо з вихідного коду; їх можна віднести і до препроцессору, і до самої мови (насправді мені складно віднести їх куди-то однозначно, так і препроцесор давно вже злився з мовою). GCC підтримує як прагми загального призначення, так і для конкретних платформ. Тема велика і цікава, також як і builtin'и, так що можливо вона буде розглянуто в окремій частині.

Безіменні поля структур і об'єднань

В структурах і об'єднаннях можна оголошувати вкладені безіменні структури та об'єднання. Поля цих вкладених структур і об'єднань будуть доступні безпосередньо
struct {
int a;
union {
int b;
float c;
};
int d;
} foo;
foo.b = 10;

Слід зазначити, що це працює тільки до тих пір, поки структура безіменна; варто дати їй ім'я, та таке оголошення перетвориться на оголошення вкладеної структури всередині простору імен яка обіймає, тобто логіка вкладеності поміняється з вкладеності даних на вкладеність просторів імен.
А якщо в опціях компілятора включити режим розширень Plan9 ("-fplan9-extensions"), то виявиться можна робити речі, доступні зараз мабуть лише в мові Go: вбудовування (embedding) одних структур в інші, що по суті є просунутою версією спадкування — в поля структури можна вбудувати цілком поля іншої структури і звертатися до них як до власних полів структури, при цьому, на відміну від спадкування С++, можна точно вказати місце, в яке повинні вбудовуватися поля (що важливо для низькорівневих цілей).
typedef struct { int a; } s1; // розширення працює тільки з typedef'амі
typedef struct { int x; s1; int y; } s2;
s2 obj;
obj.a = 10; // доступ до вбудованої структурі


Thread-Local змінні

По суті статичні змінні потоку, що зберігаються в пам'яті потоку thread-local storage. Якщо є таке явище як потоки і TLS, то має бути і ключове слово для оголошення там змінних.

Двійкові літерали

Одна з найпростіших і очевиднейших речей, яка повинна була з'явитися разом з мовою Сі в далеких сімдесятих. Але не з'явилася. Для констант используетсся префікс '0b'.
Тут варто зауважити, що для вісімкових констант варто було б ввести префікс '0o' навіть незважаючи на те, що є офіційний спосіб. Спосіб запису з початковим нулем жахливий.

Розширення gcc с++

Використання volatile

Деякі особливості використання volatile в GCC C++, відмінності від Сі.

Обмежені покажчики (з C99)

Ключове слово restrict дозволяє програмісту повідомити компілятору, що оголошений покажчик вказує на блок пам'яті, на який вказує ніякий інший покажчик. Гарантію того, що на один блок пам'яті не буде вказувати більш одного покажчика, дає програміст. При цьому оптимізуючий компілятор може генерувати більш ефективний код.
У розширенні GCC можна також створювати restrict посилання і застосовувати його для вказівника this.

«Невизначена» (vague) лінковка

Деякі конструкції в С++ вимагають місця в об'єктних файлах і можуть виявитися одночасно в декількох одиницях трансляції. Це inline-функції, таблиці віртуальних функцій (VTables), об'єкти type_info і результати инстанцирования шаблонів. GCC підтримує розміщення таких об'єктів у COMDAT секції об'єктного файлу, що дозволяє виключити дублювання об'єктів на етапі лінкування.

Прагми іnterface та implementation

Такі прагми дозволяють явно вказати компілятору, є об'єкт інтерфейсом або реалізацією. Додатковий милицю до «невизначеною лінкування».

Инстанцирование шаблонів

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

Витяг покажчика на функцію покажчика на функцію-член класу

Очевидне розширення можливостей, пов'язаних з операціями '->*' і '.*'. Якщо вказівник на поле класу на низькому рівні являє собою байтове зміщення цього поля всередині класу, то покажчик на метод — це повноцінний вказівник на функцію, і GCC додає можливість приводити тип вказівника на метод до звичайного покажчика на функцію.

Атрибути С++

Деякі атрибути (задаються через ключове слово __attribute__) застосовні тільки для C++. Кілька прикладів: abi_tag — спосіб завдання манглинга імен змінних і функцій; init_priority — пріоритет ініціалізації для глобальних об'єктів.

Оголошення безлічі версій функції

Цікава можливість. За допомогою атрибута target можна оголосити кілька версій однієї і тієї ж функції — в залежності від особливостей процесора (наприклад наявність підтримки того чи іншого набору команд, тієї або іншої моделі процесора і т. д.). Чимось схоже на умовну компіляцію, але компілюються всі функції, а потрібна вибирається під час виконання програми.

Асоційовані простору імен

Можливість еквівалентна inline namespace (тому в наступних версіях GCC нестандартна реалізація буде видалена).

Ознаки типів (Type Traits)

Підтримка на рівні компілятора спеціальних конструкцій, що дозволяють під час компіляції отримувати различню інформацію про тип. Їх досить багато, і мабуть має сенс розглянути цю тему окремо (особливо в порівнянні з аналогічними конструкціями, реалізованими на шаблонах, і аналогічними конструкціями мови D). Щоб було зрозуміло про що мова — ось кілька штук:
__is_abstract (type)
__is_base_of (base_type, derived_type)
__is_class (type)
__is_empty (type)
__is_enum (type)
__is_literal_type (тип)


Концепти С++

Найпотужніша фіча, яка не потрапила на жаль, в останній (С++17) стандарт. Спосіб явно задати обмеження на аргументи шаблонів (тобто ввести своєрідну типізацію шаблонів), і тим самим зробити метапрограмування простіше і зрозуміліше.

Не рекомендовані до використання або вже видалені можливості

Теж цікаво. Ці можливості або видалені, або оголошені не рекомендованими до використання і будуть видалені найближчим часом.
  • G++ дозволяв віртуальним функцій повертає void* бути перевантаженими функціями повертаючими інші значення покажчиків.
  • Оператори мінімуму і максимуму '<?', '>?', '<?=' і '>?=' (треба ж, виявляється і таке було)
  • Іменовані значення, що повертаються (а шкода що викинули)
  • Використання списків ініціалізації з виразом new
  • параметри шаблонів float і complex (теж незрозуміло навіщо викинули)
  • implicit typename extension (на жаль незрозуміло, що мається на увазі)
  • використання аргументів за замовчуванням в покажчиках функції, typedef'ах функцій і інших місцях, не передбачених стандартом
  • використання літералів з плаваючою крапкою для обчислення значень констант перерахувань буде видалено. Нешкідлива річ, кому завадила?
  • Ініціалізація константных полів класу типів з плаваючою точкою прямо в оголошенні класу; за стандартом там можуть бути тільки цілочисельні поля і перерахування.

Зворотна сумісність

Деякі особливості зворотної сумісності з попередніми версіями С++ і С. Ці можливості включаються спеціальними опціями компілятора.
  • Змінні, оголошені в циклі for, раніше були доступні поза циклу; з допомогою опції -fpermissive можна повернути старе поведінка.
  • Старі відмінності файли Сі не містили конструкцій extern «C»; спеціальний режим компіляції може розглядати всі такі файли, файли з мови Сі.


На цьому поки все

Як видно, деякі розширення навіть складно назвати розширеннями: це або всім відомі фічі, або — що ще гірше — милиці, покликані забезпечити сумісність з якимись давніми і успадкованими стандартами, обійти неудачые рішення в дизайні мови і т. д.
У той же час інші — воістину перлини серед мовних фіч, і дуже шкода, що їх не включають у стандарт.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.