Передача указателя на функцию, которая не соответствует требованиям формального параметра - PullRequest
5 голосов
/ 04 мая 2010
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 дюйма, и я передаю указатель на функцию, которая принимает один int, почему компилятор не жалуется?

Кроме того, поскольку это происходит, что происходит со вторым параметром y в функции invalid?

Ответы [ 6 ]

2 голосов
/ 04 мая 2010

Предполагая, что вы игнорируете все предупреждения компилятора, которые это должно дать вам, вы можете думать о том, что происходит следующим образом:

Ваш код пытается вызвать функцию, которая принимает два целых числа и возвращает одно. В зависимости от соглашения о вызовах параметры могут передаваться в регистрах на процессоре или в стеке, выходные данные, вероятно, отправляются в регистр. Вызов valid работает отлично, все там, где и ожидается. Для вызова invalid устанавливается тот же стек, с двумя параметрами, так как программа думает, что она вызывает, затем вызывается функция.

Очевидно, с вашей платформой так получается, что одиночный аргумент для invalid находится в том же месте, что и первый параметр для valid, так что invalid по совпадению делает то, что вы ожидали, вызывая его правильно. Способ очистки параметров не определен - если вызываемая функция должна очистить пространство для своих параметров, ваш стек будет записан, если вызывающая функция очистит, то ваша программа, возможно, продолжит функционировать.

Независимо от того, что вы используете здесь неопределенное поведение. Попробуйте изменить func на форму с одним параметром

int func(int(*f)(int),x){return f(x);}

и посмотрите, работают ли оба звонка.

2 голосов
/ 04 мая 2010

почему компилятор не жалуется?

Может быть, вам нужен лучший компилятор? gcc говорит warning: passing argument 1 of ‘func’ from incompatible pointer type об этом коде.

Кроме того, поскольку это происходит, что происходит со вторым параметром y в недопустимой функции?

Возможно, , что происходит, когда компилятор делает все, что обычно делает для передачи параметра (помещает его в стек, помещает в назначенный регистр и т. Д.). Вызов функции с неправильным количеством параметров - это неопределенное поведение, поэтому нет никаких гарантий - программа может аварийно завершиться или компилятор может заставить обезьян вылететь из вашего носа .

1 голос
/ 04 мая 2010

Вот пример, где неопределенное поведение работает ,
и, вероятно, что-то подобное происходит в вашем примере.

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
...
1 голос
/ 04 мая 2010

Вы хотите:

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. Ваш оригинальный код дал мне несколько предупреждений, кстати, какой компилятор вы используете? И если ваш вопрос действительно «Почему этот код работает?», То это одна из радостей неопределенного поведения.

0 голосов
/ 04 мая 2010

В C не является ошибкой приведение указателя от одного типа к другому. Однако хороший компилятор выдаст предупреждение при передаче неверного типа указателя в функцию без явного приведения. Если вы не получаете предупреждение, я настоятельно рекомендую проверить настройки вашего компилятора, чтобы убедиться, что предупреждения включены. Или рассмотрите возможность использования другого компилятора. ; -)

Чтобы понять, почему это работает, вам нужно немного понять язык ассемблера и то, как C использует стек для передачи параметров. Вы можете визуализировать стопку как большую стопку пластин, где каждая пластина содержит одну простую переменную. На многих платформах все параметры передаются в стеке. func нажмет y и x, вызовет f, а затем сбросит переменные обратно. valid загружает x и y, просматривая две верхние записи в стеке. invalid находит 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 при объявлении функции без параметров. Он сообщает компилятору, что функция действительно принимает параметры no . В скобках ничего нет, это функция с неявными параметрами.

0 голосов
/ 04 мая 2010

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

Компилятор по-прежнему генерирует код, который помещает два аргумента внутрь func для строки

return f(x, y);

так как он не знает ничего лучше. Вы хотите вызвать функцию с двумя аргументами, она выдвигает два. (при условии, что это позволяет прототип, что он и делает). Если вы проверите стек, вы увидите их, но поскольку invalid принимает только один, у вас нет прямого способа увидеть второй аргумент в C (без обмана).

...