Стандарт C не вимагає, щоб нульові вказівники знаходилися на нулі адреси машини. ЯКЩО введення 0
константи до значення вказівника повинно призвести до NULL
покажчика (§6.3.2.3 / 3), а оцінка нульового вказівника як булевого має бути помилковою. Це може бути трохи незручно , якщо ви дійсно зробити хочете нульовий адресу, а NULL
не нульовий адресу.
Тим не менш, з (важкими) модифікаціями компілятора та стандартної бібліотеки, це не неможливо NULL
бути представленим альтернативним бітовим малюнком, залишаючись при цьому строго відповідним стандартній бібліотеці. Це НЕ досить просто змінити визначення NULL
саме по собі , однак, як і тоді NULL
буде обчислюватися так.
Зокрема, вам потрібно буде:
- Впорядковуйте для прямої нулі в призначеннях покажчики (або касти до покажчиків), які слід перетворити на якесь інше магічне значення, наприклад
-1
.
- Упорядкуйте тести рівності між покажчиками та постійним цілим числом,
0
щоб перевірити, чи не магічне значення замість цього (§6.5.9 / 6)
- Упорядкуйте для всіх контекстів, у яких тип вказівника оцінюється як булевий, щоб перевірити рівність магічному значенню, а не перевірку нуля. Це випливає із семантики тестування рівності, але компілятор може реалізувати її по-різному всередині. Див. §6.5.13 / 3, §6.5.14 / 3, §6.5.15 / 4, §6.5.3.3 / 5, §6.8.4.1 / 2, §6.8.5 / 4
- Як зазначає кафе, оновіть семантику для ініціалізації статичних об'єктів (§ 6.7.8 / 10) та часткових складових ініціалізаторів (§ 6.7.8 / 21), щоб відобразити нове представлення нульових покажчиків.
- Створіть альтернативний спосіб отримати доступ до справжньої нульової адреси.
Є деякі речі, з якими не треба впоратися. Наприклад:
int x = 0;
void *p = (void*)x;
Після цього p
НЕ гарантовано є нульовим покажчиком. Потрібно обробляти лише постійні призначення (це хороший підхід для доступу до істинної нульової адреси). Аналогічно:
int x = 0;
assert(x == (void*)0); // CAN BE FALSE
Також:
void *p = NULL;
int x = (int)p;
x
не гарантовано буде 0
.
Коротше кажучи, ця сама умова, очевидно, була розглянута Комітетом з мов C, а також розглядалися питання щодо тих, хто обирає альтернативне представництво для NULL. Все, що вам потрібно зробити зараз, - це зробити значні зміни у вашому компіляторі, і ей престо, що ви зробили :)
Як бічна примітка, ці зміни можуть бути здійснені на етапі трансформації вихідного коду до відповідного компілятора. Тобто замість нормального потоку препроцесора -> компілятора -> асемблера -> лінкера, ви додасте препроцесор -> перетворення NULL -> компілятор -> асемблер -> посилання. Тоді ви можете зробити перетворення на зразок:
p = 0;
if (p) { ... }
/* becomes */
p = (void*)-1;
if ((void*)(p) != (void*)(-1)) { ... }
Для цього знадобиться повний синтаксичний аналізатор, а також аналізатор типу та аналіз typedefs та змінних оголошень, щоб визначити, які ідентифікатори відповідають покажчикам. Однак, зробивши це, можна уникнути необхідності внесення змін до частин генерації коду компілятора. Кланг може бути корисним для здійснення цього - я розумію, що він був розроблений на зразок таких перетворень. Вам, ймовірно, знадобиться внести зміни до стандартної бібліотеки.
mprotect
захистити їх. Або, якщо на платформі немає ASLR тощо, адреси, що виходять за межі фізичної пам'яті платформи. Удачі.