Как разыменование указателя функции происходит?

Почему и как делает разыменование указателя функции просто, "ничего не делают"?

Это - то, о чем я говорю:

#include

void hello() { printf("hello"); }

int main(void) { 
    (*****hello)(); 
}

Из комментария здесь:

указатели функции разыменовывают очень хорошо, но получающийся функциональный указатель будет сразу преобразован назад в указатель функции


И из ответа здесь:

При разыменовании (способом Вы думаете) указатель функции означает: доступ к памяти КОДА, поскольку это была бы Память данных.

Указатель функции не, предполагают, чтобы быть разыменованным таким образом. Вместо этого это называют.

Я использовал бы имя, "разыменовывают" бок о бок с "вызовом". Это в порядке.

Так или иначе: C разработан таким способом, которым оба идентификатора имени функции, а также указатель переменной функции захватывания означают то же: адрес для КОДИРОВАНИЯ памяти. И это позволяет переходить к той памяти при помощи вызова () синтаксис или на идентификаторе или на переменной.


Как точно разыменование указателя функции работает?

66
задан Community 23 May 2017 в 12:02
поделиться

4 ответа

Это не совсем правильный вопрос. Для C, по крайней мере, правильный вопрос:

Что происходит со значением функции в контексте rvalue?

(Контекст rvalue находится везде, где появляется имя или другая ссылка, где его следует использовать в качестве значения, а не местоположение - в основном где угодно, кроме левой стороны присваивания. Само имя происходит от правой -стороны присваивания.)

Итак, что происходит со значением функции в контекст rvalue? Он немедленно и неявно преобразуется в указатель на исходное значение функции. Если разыменовать этот указатель с помощью * , вы снова получите то же значение функции, которое немедленно и неявно преобразуется в указатель. И вы можете делать это сколько угодно раз.

Вы можете попробовать два аналогичных эксперимента:

  • Что произойдет, если разыменовать указатель функции в контексте lvalue - левой части присваивания. (Ответ будет примерно таким, чего вы ожидаете, если иметь в виду, что функции неизменяемы.)

  • Значение массива также преобразуется в указатель в контексте lvalue, но оно преобразуется в указатель на element , а не указатель на массив. Таким образом, разыменование даст вам элемент, а не массив, и безумия, которое вы показываете, не произойдет.

Надеюсь, это поможет.

P.S.Что касается , почему значение функции неявно преобразуется в указатель, ответ заключается в том, что для тех, кто использует указатели на функции, очень удобно не использовать и где угодно.Также есть двойное удобство: указатель функции в позиции вызова автоматически преобразуется в значение функции, поэтому вам не нужно писать * для вызова через указатель функции.

П.П.С. В отличие от функций C, функции C ++ могут быть перегружены, и я не уполномочен комментировать, как семантика работает в C ++.

51
ответ дан 24 November 2019 в 15:07
поделиться

C++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.

7
ответ дан 24 November 2019 в 15:07
поделиться

Поставьте себя на место автора-составителя. Указатель функции имеет четко определенное значение, это указатель на большой двоичный объект байтов, представляющих машинный код.

Что делать, когда программист разымещает указатель функции? Вы берете первый (или 8) байт машинного кода и переинтерпретируете его как указатель? Вероятность того, что это не сработает, составляют около 2 миллиардов к одному. Вы декларируете UB? Многое из этого уже происходит. Или вы просто игнорируете попытку? Вы знаете ответ.

2
ответ дан 24 November 2019 в 15:07
поделиться

Как именно работает ли разыменование указателя функции?

Два шага. Первый шаг - во время компиляции, второй - во время выполнения.

На первом этапе компилятор видит, что у него есть указатель и контекст, в котором разыменовывается этот указатель (например, (* pFoo) () ), поэтому он генерирует код для этой ситуации, код, который будет использоваться на шаге 2.

На шаге 2 код выполняется во время выполнения. Указатель содержит несколько байтов, указывающих, какая функция должна выполняться следующей. Эти байты каким-то образом загружаются в ЦП. Обычный случай - это ЦП с явной инструкцией CALL [регистр] . В таких системах указатель функции может быть просто адресом функции в памяти, а код разграничения не делает ничего, кроме загрузки этого адреса в регистр, за которым следует инструкция CALL [регистр] .

1
ответ дан 24 November 2019 в 15:07
поделиться