Страницы

Поиск по вопросам

четверг, 19 декабря 2019 г.

Синтаксис вызова функции указателем

#c


Меня мучает вопрос, с какой целью это сделано?

Есть такой указатель: int (*a)() = ownfucntion; /// ownfucntion - любая пользовательская
функция.

Мы можем эту функцию вызвать так: a();

А можем так: (*a)();

Где логика? Обычно разименовывание используется для получения значения из указателя.
А тут результат одинаков: хочешь разименовывай, хочешь не разименовывай в итоге будет
вызвана функция.
    


Ответы

Ответ 1



Обозначение функции всегда за исключением одного случая преобразуется в указатель на функцию. Из стандарта C (6.3.2.1 Lvalues, arrays, and function designators) 4 A function designator is an expression that has function type. Except when it is the operand of the sizeof operator65) or the unary & operator, a function designator with type ‘‘function returning type’’ is converted to an expression that has type ‘‘pointer to function returning type’’. Хотя в этой цитате написано про использование оператора sizeof, но на самом деле функция не может использоваться в операторе sizeof. Стандарт C (6.5.3.4 The sizeof and alignof operators) 1 The sizeof operator shall not be applied to an expression that has function type or an incomplete type, to the parenthesized name of such a type, or to an expression that designates a bit-field member Поэтому когда используется постфиксное выражение вызова функции, то обозначение функции неявно преобразуется к указателю на функцию (6.5.2.2 Function calls) 1 The expression that denotes the called function92) shall have type pointer to function returning void or returning a complete object type other than an array type. В связи с этим можно написать даже такое выражение, как показано в следующей демонстрационной программе #include void f( void ) { puts( "Oh...At last I am called!" ); } int main(void) { ( **********f)(); return 0; } Вывод программы на консоль: Oh...At last I am called! Потому что согласно стандарту C (6.5.3.2 Address and indirection operators) 4 The unary * operator denotes indirection. If the operand points to a function, the result is a function designator; if it points to an object, the result is an lvalue designating the object. If the operand has type ‘‘pointer to type’’, the result has type ‘‘type’’. If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined.102 То есть сначала обозначение функции преобразуется неявно к указателю на функцию. Затем после применения оператора * этот указатель преобразуется к обозначению функции и сразу же снова преобразуется в указатель на функцию и т.д. Вы можете применить столько операторов * к обозначению функцию, сколько позволит конкретный компилятор Подытоживая, можно сказать следующее. Для объявления функций вводится их идентификатор, чтобы по нему можно было ссылаться на функции и отличать одну функцию от другой. При этом сама функция может быть еще не определена, а лишь только объявлена. Когда этот идентификатор функции или, другими словами, обозначение функции используется в выражениях, то оно преобразуется к типу указателя на функцию. В выражениях используются значения. Какое значение назначить обозначению функции, используемому в выражениях? Естественно разумно в качестве значения использовать адрес функции, потому что все, для чего создаются функции, это для их выполнения. А выполнить функцию можно, лишь передав управление по тому адресу, где эта функция определена. Поэтому в качестве значения функции и принят ее адрес, то есть функция в выражениях преобразуется к указателю на саму себя. Единственным исключением по отношению к неявному преобразования функции к указателю на функцию является использование оператора &. Здесь нет необходимости преобразовывать функцию неявно к указателю на нее, так как применение оператора & итак является явным взятием адреса функции.

Ответ 2



В стандартном С вызов функции вообще всегда делается именно и только через указатель на функцию, т.е. оператор () требует именно указательного операнда. А тип "функция" во всех контекстах, кроме sizeof, _Alignof и унарного &, автоматически неявно преобразуется к типу "указатель на функцию" (явление, часто называемое function type decay). Таком образом даже когда вы пишете ownfunction() на самом деле происходит неявное преобразование (int (*)()) ownfunction и оператор () применяется уже именно к результату этого преобразования. Попытка явного применения унарного оператора * к указателю на функцию приводит к тому, что результат этого применения (типа "функция") сразу же снова автоматически "сваливается" обратно в тип "указатель" (за исключением вышеупомянутых контекстов). А отсюда уже побочным эффектом следует как возможность опционального/"бесконечного" применения унарного оператора * к указателю на функцию, так и опциональность применения унарного оператора & при явном получении указателя на функцию. (Т.е. для иллюстрации этого поведения оператора * совсем не обязательно заводить явный указатель a на функцию. Оно прекрасно иллюстрируется на примере самой функции.) P.S. Ситуация во многом симметрична ситуации с массивами (array type decay), с той только разницей, что массив неявно преобразуется к типу "указатель на элемент", а не "указатель на весь массив", что предотвращает возможность "бесконечного" применения унарного оператора * к массиву. Но общая идея - та же. Что интересно, в доисторических версиях языка С (см. "C Reference Manual") Деннис Ритчи определял поведение оператора () именно и только через тип "функция", т.е. слева от () должно было указываться именно выражение типа "функция", а не "указатель на функцию". И именно в этом контексте неявного преобразования типа "функция" к типу "указатель на функцию" не происходило. Это где-то позже (в K&R C?) оператор () вдруг стал требовать операнда типа "указатель на функцию". Почему произошел такой переход? Трудно сказать. Возможно авторов языка привлекла возможность унификации function type decay с array type decay. На это также неявно указывает то, что в список контекстов-исключений включили оператор sizeof, тем самым сделав выражение sizeof функция нелегальным. Казалось бы, зачем было включать sizeof в список исключений? Можно было бы разрешить function type decay под sizeof и sizeof функция спокойно возвращало бы размер указателя на функцию. Но нет. Вероятная причина - унификация decay-поведения с массивами.

Ответ 3



Выполняется неявное приведение. Имя функции и ее адрес полностью взаимозаменяемы, а соответственно, вытекает и необходимость возможности применения & (и *) к имени и к адресу функции. Например, допустимы оба варианта: int (*a)() = ownfucntion; int (*a)() = &ownfucntion; Ну, а вызывать можете даже так :) (********************&********************a)();

Комментариев нет:

Отправить комментарий