Зачем использовать параметр функции «foo» следующим образом: * (& foo)?

Фрагмент кода в ядре Linux 0.12 использует параметр функции следующим образом:

int do_signal(int signr, int eax /* other parameters... */) { /* ... */ *(&eax) = -EINTR; /* ... */ } 

objective кода – поместить -EINTR в память, где живет eax, но я не могу сказать, почему это не сработает, если просто назначить eax:

 eax = -EINTR 

Как компилятор может сделать разницу между eax и * (& eax) ?

Одним из возможных намерений может быть сохранение переменной eax из регистра. Если мы посмотрим на проектный стандарт C99, мы увидим, что раздел 6.5.3.2 адреса и косвенности говорят ( акцент мой ):

Унарный оператор & дает адрес своего операнда. […] Если операнд является результатом унарного * оператора, ни этот оператор, ни оператор & не оцениваются, и результат как бы опускался , за исключением того, что ограничения на операторы все еще применяются, а результат не является lvalue. […]

в сноске 87 говорится ( акцент мой идет вперед ):

Таким образом, & * E эквивалентно E (даже если E – нулевой указатель) и & (E1 [E2]) – ((E1) + (E2)). Всегда верно, что если E является обозначением функции или значением l, которое является допустимым операндом унарного оператора &, * & E является обозначением функции или значением l, равным E.

мы найдем следующее ограничение на & operator :

Операнд унарного оператора & должен быть либо обозначением функции, результатом оператора [], либо унарного *, либо значением l, которое обозначает объект, который не является битовым полем и не объявлен с помощью спецификатора classа хранения регистров ,

Это имеет смысл, поскольку мы не можем взять адрес регистра, и поэтому, выполнив адрес операции, они, возможно, пытались помешать компилятору полностью выполнять операции в регистрах и гарантировать, что данные в определенных ячейках памяти будут изменены.

Как указывает ouah, это не мешает компилятору оптимизировать то, что эффективно не работает , но как описано в GCC-хаках в ядре Linux . Linux опирался на многие расширения gcc и считал, что 0.12 – это очень старое kernel, gcc возможно, гарантировало такое поведение или, возможно, случайно сработало таким образом, но я не могу найти документацию, которая так говорит.

Старая Linux, которую вы отправили, пыталась выполнить очень хрупкий взлом. Функция была определена следующим образом:

 int do_signal(long signr,long eax,long ebx, long ecx, long edx, long orig_eax, long fs, long es, long ds, long eip, long cs, long eflags, unsigned long * esp, long ss) 

Аргументы функции фактически не представляют аргументы функции (кроме signr ), но значения, вызывающие функцию (обработчик прерываний ядра / обработчик исключений, записанных в сборке), сохраняются в стеке перед вызовом do_signal . Оператор *(&eax) = -EINTR предназначен для изменения сохраненного значения EAX в стеке. Аналогично утверждение *(&eip) = old_eip -= 2 предназначено для изменения обратного адреса обработчика вызова. После do_signal обработчик do_signal первые 9 «аргументов» из стека, восстанавливая их в названные регистры. Затем он выполняет инструкцию IRETD которая IRETD оставшиеся аргументы из стека и возвращается в пользовательский режим.

Излишне говорить, что этот хак невероятно ненадежный. Это зависит от кода генерации компилятора точно так, как они ожидали. Я удивлен, что он даже работал компилятором GCC той эпохи, я сомневаюсь, что задолго до того, как GCC представил оптимизацию, которая сломала его.