Почему этот указатель на работу работает без предупреждений или ошибок?
Зная, что этот вызов:
pow(4);
создаст это сообщение об ошибке:
error: too few arguments to function ‘pow’
Я изучаю указатели на функции, и я удивился, увидев этот код ниже. Но почему?
#include<stdio.h>
#include<math.h>
void aux(double (*function)(), double n, double x);
int main(void)
{
aux(pow, 4, 2);
aux(sqrt, 4, 0);
return 0;
}
void aux(double (*function)(double), double n, double x)
{
if(x == 0)
printf("\nsqrt(%.2f, %.2f): %f\n", n, x, (*function)(n));
else
printf("\npow(%.2f, %.2f): %f\n", n, x, (*function)(n));
}
Я скомпилировал, используя:
gcc -Wall -Wextra -pedantic -Wconversion -o test test.c -lm
Результат:
pow(4.00, 2.00): 16.000000
sqrt(4.00, 0.00): 2.000000
Если я изменил третий параметр первого вызова aux
на 3, результат изменится на:
pow(4.00, 3.00): 64.000000
sqrt(4.00, 0.00): 2.000000
И еще один вопрос. Каков правильный способ объявления и использования указателей на функции в этом случае?
Ответы
Ответ 1
Это:
void aux(double (*function)(), double n, double x);
использует декларацию не-прототипа старого стиля для function
. Пустые круглые скобки ()
означают, что функция принимает фиксированное, но неуказанное число и тип аргументов.
C по-прежнему разрешает такую декларацию для обратной совместимости. Прототипы (объявления функций, указывающие типы параметров) были введены ANSI C в 1989 году. До этого было невозможно указать типы параметров в объявлении функции, и компиляторы не смогли проверить, прошел ли вызов правильное число и тип аргументов.
Такие декларации являются "устаревшими", что означает, что поддержка для них может быть удалена из будущего стандарта C (но в течение более 20 лет комитет не собирался их удалять). Вызов функции с неправильным количеством типов аргументов не обязательно будет диагностироваться компилятором, а поведение - undefined.
Правила совместимости типов функций немного сложны, если у вас есть прототип, а другой нет. Эти типы:
double(double) /* function with one double parameter
returning double */
double(double, double) /* function with two double parameters
returning double */
несовместимы друг с другом, но оба они совместимы с этим типом:
double() /* function with a fixed but unspecified number of parameters
returning double */
что позволяет иметь неправильные вызовы без диагностики из компилятора.
Чтобы избежать этой проблемы, всегда используйте прототипы:
void aux(double (*function)(double, double), double n, double x);
Вы не только получаете лучшую диагностику от своего компилятора, вам не нужно беспокоиться о правилах свернутой совместимости для не-прототипированных функций (которые, если вам интересно, указаны в N1570 6.7.6.3, пункт 16).
Ответ 2
Ваш прототип функции aux()
...
void aux(double (*function)(), double n, double x);
... указывает первый аргумент как указатель на функцию, возвращающую double
и принимающую неуказанные аргументы. Это предотвращает передачу GCC предупреждений о несоответствующих типах для вызовов этой функции в main()
.
Однако определение функции aux()
дает более конкретный тип для его первого параметра, который несовместим с фактическими аргументами, которые вы передаете. Вызов этих функций с помощью указателя имеет undefined семантику. Довольно многое может случиться, в том числе, что поведение похоже на то, что вы хотели. Вы не можете полагаться на что-либо об этом.
Ответ 3
Потому что вы указали прототип aux()
, прежде чем main и function
не имеют каких-либо типов аргументов. Узнайте разницу:
void f(); /* Accepts any number of arguments thanks to K&R C */
void g(void); /* No arguments accepted */
void h(int); /* Only one integer argument accepted */
Если вы объявите прототип aux()
следующим образом:
void aux(double (*function)(double), double n, double x);
GCC начинает жаловаться.
Ответ 4
Пустая пара скобок ()
сообщает компилятору C, что функция может вызываться с любым количеством параметров, но сама функция имеет определенное количество параметров для своего прототипа. Поэтому, если использовать его с указателем функции, а число и соответствующие типы переданных параметров совпадают с прототипом направленной функции, все будет работать. Если вы голодаете список параметров или используете по-разному типизированные значения, хотя... хорошо, что поведение undefined.