Переваг такого коду може бути багато, але, на жаль, Стандарт С не був написаний для його полегшення. Компілятори історично пропонували ефективні гарантії поведінки, ніж те, що вимагав Стандарт, що давав можливість писати такий код набагато чіткіше, ніж це можливо в Стандарті С, але останнім часом компілятори почали скасовувати такі гарантії в ім'я оптимізації.
Найголовніше, що багато компіляторів С історично гарантували (за задумом, якщо не документацією), що якщо два типи структури містять однакову початкову послідовність, то вказівник на будь-який тип може використовуватися для доступу до членів цієї загальної послідовності, навіть якщо типи не пов'язані між собою, і далі, що для встановлення загальної початкової послідовності всі вказівники на структури є рівнозначними. Код, який використовує таку поведінку, може бути набагато більш чистим і безпечним для типів, ніж код, який цього не робить, але, на жаль, хоча Стандарт вимагає, щоб структури, що мають спільну початкову послідовність, повинні бути викладені так само, він забороняє коду фактично використовувати вказівник одного типу для доступу до початкової послідовності іншого.
Отже, якщо ви хочете написати об'єктно-орієнтований код на C, вам доведеться вирішити (і слід прийняти це рішення на початку) або перестрибнути через безліч обручів, щоб дотримуватися правил типу вказівника C, і бути готовим мати сучасні компілятори генерують безглуздий код, якщо один прослизає вгору, навіть якщо старші компілятори створили б код, який працює за призначенням, або ж документують вимогу, що код може бути використаний лише для компіляторів, налаштованих для підтримки поведінки вказівника старого стилю (наприклад, використання "-но-строгий-згладжування") Деякі люди вважають "-но-суворий-згладжуючий" злом, але я б припустив, що корисніше думати про "-фно-строгий-згладжування" як мову, яка пропонує більшу смислову потужність для деяких цілей, ніж "стандартний" C,але за рахунок оптимізацій, які можуть бути важливими для деяких інших цілей.
Наприклад, на традиційних компіляторах історичні компілятори інтерпретують такий код:
struct pair { int i1,i2; };
struct trio { int i1,i2,i3; };
void hey(struct pair *p, struct trio *t)
{
p->i1++;
t->i1^=1;
p->i1--;
t->i1^=1;
}
як виконання наступних кроків для того, щоб: збільшити перший член *p, доповнити найнижчий біт першого члена *t, потім декрементувати перший член *pі доповнити найнижчий біт першого члена *t. Сучасні компілятори переставлять послідовність операцій таким чином, який код, який буде більш ефективним, якщо pі tідентифікувати різні об'єкти, але змінить поведінку, якщо вони цього не роблять.
Цей приклад, звичайно, навмисно продуманий, і на практиці код, який використовує вказівник одного типу для доступу до членів, що входять до загальної початкової послідовності іншого типу, як правило, спрацює, але, на жаль, не існує способу знати, коли такий код може вийти з ладу безпечно використовувати його взагалі неможливо, окрім відключення аналізу псевдонімів на основі типу.
Дещо менш надуманий приклад виникне, якби хотілося написати функцію, щоб зробити щось на зразок підміна двох покажчиків на довільні типи. У переважній більшості компіляторів "C" 1990-х років, що може бути здійснено за допомогою:
void swap_pointers(void **p1, void **p2)
{
void *temp = *p1;
*p1 = *p2;
*p2 = temp;
}
Однак у стандарті C потрібно використовувати:
#include "string.h"
#include "stdlib.h"
void swap_pointers2(void **p1, void **p2)
{
void **temp = malloc(sizeof (void*));
memcpy(temp, p1, sizeof (void*));
memcpy(p1, p2, sizeof (void*));
memcpy(p2, temp, sizeof (void*));
free(temp);
}
Якщо *p2він зберігається у виділеному сховищі, а тимчасовий покажчик не зберігається у виділеному сховищі, ефективним типом *p2стане тип тимчасового вказівника та код, який намагається використовувати *p2як будь-який тип, який не відповідає тимчасовому вказівнику тип буде викликати не визначене поведінку. Слід бути впевненим вкрай малоймовірно, що компілятор помітить таке, але оскільки сучасна філософія компілятора вимагає від програмістів уникати не визначеної поведінки будь-якою ціною, я не можу придумати жодного іншого безпечного способу написання вищевказаного коду без використання виділеного сховища .