Как происходит разыменование функционального указателя?
Почему и как разыменование указателя функции просто "ничего не делает"?
Вот о чем я говорю:
#include<stdio.h>
void hello() { printf("hello"); }
int main(void) {
(*****hello)();
}
Из комментария здесь:
указатели на функцию отлично, но получающаяся функция указатель будет немедленно преобразован обратно в указатель функции
И из ответа здесь:
Разыменование (как вы думаете) Функция указателя означает: доступ к CODE, поскольку это будет ДАННЫЕ память.
Указатель функций не должен быть разыменован таким образом. Вместо этого называется.
Я бы использовал имя "разыменовать" сторону рядом с "вызовом". Это нормально.
В любом случае: C сконструирован таким образом что оба имени имени функции как а также функция переменной удержания указатель означает то же самое: адрес КОД Память. И это позволяет перейти к такому памяти с использованием синтаксиса call() либо по идентификатору или переменной.
Как точно работает разыменование указателя функции?
Ответы
Ответ 1
Это не совсем правильный вопрос. Для C, по крайней мере, правильный вопрос:
Что происходит с значением функции в контексте rvalue?
(Контекст rvalue - это где-нибудь имя или другая ссылка, где он должен использоваться как значение, а не местоположение; в основном где угодно, кроме левой стороны задания. Само название происходит от права, с правой стороны задания.)
ОК, так что происходит с значением функции в контексте rvalue? Он немедленно и неявно преобразуется в указатель на исходное значение функции. Если вы разыщите этот указатель с помощью *
, вы снова получите одно и то же значение функции, которое немедленно и неявно преобразуется в указатель. И вы можете делать это столько раз, сколько хотите.
Два аналогичных эксперимента, которые вы можете попробовать:
-
Что произойдет, если вы разыщите указатель на функцию в lvalue context — левая часть задания. (Ответ будет о том, чего вы ожидаете, если вы помните, что функции неизменяемы.)
-
Значение массива также преобразуется в указатель в контексте lvalue, но преобразуется в указатель на тип элемента, а не на указатель на массив. Таким образом, разыменование этого объекта даст вам элемент, а не массив, и безумие, которое вы показываете, не происходит.
Надеюсь, что это поможет.
P.S. Что касается того, почему значение функции неявно преобразуется в указатель, ответ таков, что для тех, кто использует указатели на функции, для удобства использовать &
повсеместно. Там также есть двойное удобство: указатель функции в позиции вызова автоматически преобразуется в значение функции, поэтому вам не нужно писать *
для вызова указателя функции.
P.P.S. В отличие от функций C, функции С++ могут быть перегружены, и я не могу комментировать, как семантика работает на С++.
Ответ 2
С++ 03 §4.3/1:
Значение l типа функции T может быть преобразовано в r-значение типа "указатель на T". Результатом является указатель на функцию.
Если вы попытаетесь выполнить недопустимую операцию над ссылкой на функцию, например, унарный оператор *
, первое, что пытается попробовать язык, это стандартное преобразование. Это похоже на преобразование int
при добавлении его в float
. Использование *
в ссылке на функцию заставляет язык вместо этого указывать свой указатель, который в вашем примере равен квадрату 1.
Другим случаем, когда это применимо, является назначение указателя функции.
void f() {
void (*recurse)() = f; // "f" is a reference; implicitly convert to ptr.
recurse(); // call operator is defined for pointers
}
Обратите внимание, что это не работает по-другому.
void f() {
void (&recurse)() = &f; // "&f" is a pointer; ERROR can't convert to ref.
recurse(); // OK - call operator is *separately* defined for references
}
Функциональные ссылочные переменные хороши, потому что они (теоретически, я никогда не тестировал) намекают на компилятор, что косвенная ветка может быть ненужной, если она инициализирована в охватывающей области.
В C99 разыменование указателя функции дает обозначение функции. §6.3.2.1/4:
Обозначение функции - это выражение, имеющее тип функции. За исключением случаев, когда он является операндом оператора sizeof или унарного и оператора, обозначение функции с типом возвращаемого типа типа '' преобразуется в выражение, которое имеет указатель типа '' для возвращаемого типа функции.
Это больше похоже на ответ Нормана, но особенно C99 не имеет понятия rvalues.
Ответ 3
Поставьте себя на место писателя-компилятора. Указатель функции имеет четко определенное значение, это указатель на блок-схему байтов, представляющих машинный код.
Что вы делаете, когда программист разыгрывает указатель на функцию? Вы берете первые (или 8) байты машинного кода и интерпретируете это как указатель? Коэффициенты составляют около 2 миллиардов к одному, что это не сработает. Вы объявляете UB? Многое уже происходит. Или вы просто игнорируете попытку? Вы знаете ответ.
Ответ 4
Как точно работает разыменование указателя функции?
Два шага. Первый шаг - во время компиляции, второй - во время выполнения.
На первом этапе компилятор видит, что у него есть указатель и контекст, в котором этот указатель разыменован (например, (*pFoo)()
), поэтому он генерирует код для этой ситуации, код, который будет использоваться на шаге 2.
На шаге 2 во время выполнения выполняется код. Указатель содержит несколько байт, указывающих, какая функция должна быть выполнена следующей. Эти байты каким-то образом загружаются в CPU. Частым случаем является процессор с явной инструкцией CALL [register]
. В таких системах указатель на функцию может быть просто адресом функции в памяти, а код дефрагментации выполняет не что иное, как загрузку этого адреса в регистр, за которым следует инструкция CALL [register]
.