int valid (int x, int y) {
return x + y;
}
int invalid (int x) {
return x;
}
int func (int *f (int, int), int x, int y) {
//f is a pointer to a function taking 2 ints and returning an int
return f(x, y);
}
int main () {
int val = func(valid, 1, 2),
inval = func(invalid, 1, 2); // <- 'invalid' does not match the contract
printf("Valid: %d\n", val);
printf("Invalid: %d\n", inval);
/* Output:
* Valid: 3
* Invalid: 1
*/
}
В строке inval = func(invalid, 1, 2);
, почему я не получаю ошибку компилятора? Если func
ожидает указатель на функцию, берущую 2 ints, и я передаю указатель на функцию, которая берет единственный интервал, почему компилятор не жалуется?
Кроме того, так как это происходит, что происходит со вторым параметром y
в invalid
функция?
почему компилятор не жалуется?
Может вам нужен компилятор получше? gcc сообщает предупреждение: передача аргумента 1 «func» из несовместимого типа указателя
в этом коде.
Кроме того, поскольку это происходит, что происходит со вторым параметром y в недопустимой функции?
Вероятно происходит то, что компилятор делает то, что обычно делает, чтобы передать параметр (нажмите его на стек, поместите его в назначенный регистр и т. д.). Однако вызов функции с неправильным числом параметров является неопределенным поведением, поэтому нет никаких гарантий - программа может вылететь из строя или компилятор может заставить обезьян вылететь из вашего носа .
Предполагая, что вы игнорируете все предупреждения компилятора, которые это должно вам выдать, вы можете подумайте о том, что происходит следующим образом:
Ваш код пытается вызвать функцию, которая принимает два целых числа и возвращает один. В зависимости от соглашения о вызовах параметры могут передаваться в регистрах процессора или в стеке, вывод, вероятно, идет в регистр. Действительный вызов
работает нормально, все происходит там, где ожидается. Для недопустимого вызова
устанавливается тот же стек с двумя параметрами, такими, как программа считает, что он вызывает, затем вызывается функция.
По-видимому, с вашей платформой так получилось, что единственный аргумент для недействительный
находится в том же месте, что и первый параметр для действительный
, так что недопустимый
случайно делает то, что вы ожидали, называя его правильно. Как очищаются параметры, не указано - если вызываемая функция должна очистить пространство для своих параметров, ваш стек будет поджарен, если вызывающая функция очистится, ваша программа, возможно, продолжит работу.
Независимо от того, здесь вы вызываете неопределенное поведение. Попробуйте изменить func
на однопараметрическую форму
int func(int(*f)(int),x){return f(x);}
и посмотрите, работают ли по-прежнему оба вызова.
Вот пример, в котором неопределенное поведение работает ,
и, вероятно, что-то подобное происходит в вашем примере.
typedef int (*p)(int, int);
typedef int (*p2)(int);
int invalid (int x) {
return x;
}
int func () {
p2 f = invalid;
return ((p)f)(1, 2);
}
// IA32 asm, "func"
...
216: p2 f = invalid;
00402148 mov dword ptr [ebp-4],offset @ILT+1380(invalid) (00401569)
0040214F mov eax,dword ptr [ebp-4]
00402152 mov dword ptr [ebp-4],eax
217: return ((p)f)(1, 2);
00402155 mov esi,esp
00402157 push 2 ; <--
00402159 push 1 ; <--
0040215B call dword ptr [ebp-4] ; "invalid" will use only "1"
0040215E add esp,8 ; <-- `pop` the arguments
...
что происходит со вторым параметром y в недопустимой функции?
Компилятор по-прежнему генерирует код, который помещает два аргумента внутрь func
для строки
return f(x, y);
, поскольку он не знает ничего лучше . Вы хотите вызвать функцию с двумя аргументами, она подталкивает два. (при условии, что прототип позволяет, что он и делает). Если вы проверите стек, вы увидите их, но поскольку invalid
принимает только один, у вас нет прямого способа увидеть второй аргумент в C (без обмана).
В C приведение указателя от одного типа к другому не является ошибкой. Однако хороший компилятор выдаст предупреждение при передаче неправильного типа указателя в функцию без явного приведения. Если вы не получаете предупреждения, я настоятельно рекомендую проверить настройки вашего компилятора, чтобы убедиться, что предупреждения включены.Или рассмотрите возможность использования другого компилятора. ; -)
Чтобы понять, почему это работает, вам нужно немного понять язык ассемблера и то, как C использует стек для передачи параметров. Вы можете визуализировать стопку как большую стопку тарелок, где каждая тарелка содержит одну простую переменную. На многих платформах все параметры передаются в стек. func
помещает y
и x
, вызывает f
, а затем возвращает переменные обратно. допустимый
загружает x
и y
, глядя на две верхние записи в стеке. недопустимый
находит x
, просматривая верхнюю запись в стеке.
Вот как может выглядеть стек внутри недопустимого:
main: 3
uninitialized
f: 2
1
invalid
invalid: 2
1
invalid ()
принимает один параметр, поэтому он просто смотрит на вершину стека (1) и загружает его как параметр.
Так же работают такие функции, как printf
. Они могут принимать переменное количество параметров. Первый параметр находится наверху стека, и они могут просто смотреть вниз в поисках нужного количества параметров. Некоторые системы передают некоторые параметры в регистрах вместо использования стека, но это работает аналогично.
В самые ранние дни C объявления функций вообще не включали параметры. Фактически, если вы объявляете функцию без каких-либо скобок, вы все равно можете определить функцию таким образом. Например, это прекрасно компилируется:
void foo();
void bar(void) {
foo(5); /* foo's parameters are implicit */
}
Вот почему важно включить void
при объявлении функции без параметров.Он сообщает компилятору, что функция действительно не принимает параметров . В скобках ничего нет, это функция с неявными параметрами.
Вы хотите:
int func (int (*f) (int, int), int x, int y) {
То, что у вас в коде является типом функции, возвращающей int * - вы хотите указатель на функцию, возвращающую int. После этого изменения эта строка:
inval = func(invalid, 1, 2);
дает мне:
fp.c:16: warning: passing argument 1 of 'func' from incompatible pointer type
fp.c:9: note: expected 'int (*)(int, int)' but argument is of type 'int (*)(int)'
с gcc. Ваш оригинальный код также выдал мне несколько предупреждений, BTW - какой компилятор вы используете? И если ваш вопрос действительно "Почему этот код, кажется, работает?", что ж, это одна из радостей неопределенного поведения.