2015-07-17 6 views
2

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

#include <iostream> 

typedef int (*test_func_t) (int, int, int); 

int print_integer (int a) 
{ 
    std::cout << "num: " << a << "\n"; 
    return a; 
} 

int main (int argc, char * argv[]) 
{ 
    test_func_t func = (test_func_t) &print_integer; 
    std::cout << "calling with 3 parameters func(5,7,9)\n"; 
    func(5,7,9); 
    return 0; 
} 

Как вы можете видеть, тип (test_func_t) определяется как функция с 3 ИНТ аргументами. Указателю функции (func) присваивается указатель на «print_integer», который получает только один аргумент, а затем указатель функции вызывается с тремя аргументами (5, 7, 9).

Этот код работает и производит выход «num: 5».

GDB disas выход (синтаксис Intel)

disas main 
... 
    0x080486cb <+9>:  mov DWORD PTR [esp+0x1c],0x804867d 
... 
    0x080486e0 <+37>: mov DWORD PTR [esp+0x8],0x9 
    0x080486e8 <+45>: mov DWORD PTR [esp+0x4],0x7 
    0x080486f0 <+53>: mov DWORD PTR [esp],0x5 
    0x080486f7 <+60>: mov eax,DWORD PTR [esp+0x1c] 
    0x080486fb <+64>: call eax 

disas print_integer 
    ... 
    0x08048683 <+6>:  mov DWORD PTR [esp+0x4],0x8048830 
    0x0804868b <+14>: mov DWORD PTR [esp],0x8049ae0 
    0x08048692 <+21>: call 0x8048530 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt> 
    0x08048697 <+26>: mov edx,DWORD PTR [ebp+0x8] 
    0x0804869a <+29>: mov DWORD PTR [esp+0x4],edx 
    0x0804869e <+33>: mov DWORD PTR [esp],eax 
    0x080486a1 <+36>: call 0x80484d0 <std::ostream::operator<<(int)@plt> 

Как вы можете видеть, остальные аргументы ([EBP + 0x12] и [EBP + 0x16]) просто не используется.

Мои вопросы:

  1. Это, кажется, работает на Linux x86 с __cdecl соглашение о вызове. Безопасно ли это для других архитектур и вызывать соглашения?
  2. Предоставляет ли какой-либо стандарт C/C++/определяет результат назначения указателя функции из функции, которая ожидает меньше аргументов?

Пример такого использования: Node.js-х NODE_MODULE регистрирует функцию, type имеет 3 аргументы [экспорта, модуль, Priv]. Это called with those 3, но формальные примеры показывают регистрацию функции с аргументами 1 или 2.

+6

Нет и нет. Это неопределенное поведение. И это нигде не безопасно. –

+2

Это не совсем определенно и, в общем, плохая идея и небезопасна. Хотя, с учетом сказанного, это была довольно распространенная практика, и в соответствии с соглашениями о вызовах, где она работает, она работает нормально. –

+0

@EugeneSh. «Небезопасно» - я полностью согласен с этим. Есть ли пример, который заставляет этот тип вызова «ломать» вещи? (помимо явно неправильного использования и злоупотребления) –

ответ

12

Цитирование из C++ 11 стандартного expr.reinterpret.cast 6:

Функция указатель может быть явно преобразован в указатель на функцию другого типа. Эффект вызова функции с помощью указателя на тип функции (8.3.5), который не совпадает с типом, используемым в определении функции не определено.

Итак, я бы сказал, что это не безопасно в целом. Это неопределенное поведение. Тем не менее, я не знаю, как ведет себя другая реализация C++ в этой ситуации.

3

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

// func(5,7,9); 
push 9 
push 7 
push 5 
call func 
---> func: 
    push bp 
    mov bp, sp 
    mov ax, [bp+8] ; get 5 

// stack: 
9 
7 
5      [bp+8] 
<return address>  [bp+4] 
<value of old bp>  ^
    new bp: -------------+ 
+4

Это _might_ работает для вызовов '__cdecl' ... реализация может добавить средства проверки времени выполнения для проверки такого неопределенного поведения и отмены во время выполнения, или оптимизатор может решить, что то, что вы пытались сделать, не определено и полностью удаляет вызов. Это радость неопределенного поведения. –

+1

@ Jonathan-wakely, реализация C не разрешена. Язык C позволяет это, и это инструмент при расположении программиста. Это также требуется для обратной совместимости с версиями C, которые не использовали прототипы. В цитате Паоло также говорится, что _effect_ не определен, что означает, что стандарт не предписывает никакого поведения и оставляет его программисту, что является правильным. Стандартные, а также умные варианты внедрения должны сочетаться с моим программированием, особенно когда я хорошо знаю, что делаю. –

+0

Я уверен, что программа с '#include ' не C, хотя. Возможно, квалифицируйте свой ответ, сказав, что для C и C++ правила различны. –

 Смежные вопросы

  • Нет связанных вопросов^_^