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

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 функция?

5
задан Andreas Grech 4 May 2010 в 10:32
поделиться

6 ответов

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

Может вам нужен компилятор получше? gcc сообщает предупреждение: передача аргумента 1 «func» из несовместимого типа указателя в этом коде.

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

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

2
ответ дан 14 December 2019 в 13:29
поделиться

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

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

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

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

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

и посмотрите, работают ли по-прежнему оба вызова.

2
ответ дан 14 December 2019 в 13:29
поделиться

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

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
ответ дан 14 December 2019 в 13:29
поделиться

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

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

return f(x, y);

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

0
ответ дан 14 December 2019 в 13:29
поделиться

В 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 при объявлении функции без параметров.Он сообщает компилятору, что функция действительно не принимает параметров . В скобках ничего нет, это функция с неявными параметрами.

0
ответ дан 14 December 2019 в 13:29
поделиться

Вы хотите:

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

1
ответ дан 14 December 2019 в 13:29
поделиться
Другие вопросы по тегам:

Похожие вопросы: