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

Я только начинаю переносить голову вокруг указателей функции в C. Понять, как кастинг работ указателей функции, я записал следующую программу. Это в основном создает указатель функции к функции, которая берет один параметр, бросает его к указателю функции с тремя параметрами и вызывает функцию, предоставляя три параметра. Мне было любопытно, что произойдет:

#include 

int square(int val){
  return val*val;
}

void printit(void* ptr){
  int (*fptr)(int,int,int) = (int (*)(int,int,int)) (ptr);
  printf("Call function with parameters 2,4,8.\n");
  printf("Result: %d\n", fptr(2,4,8));
}


int main(void)
{
    printit(square);
    return 0;
}

Это компилирует и работает без ошибок или предупреждений (gcc - Стена на Linux / x86). Вывод в моей системе:

Call function with parameters 2,4,8.
Result: 4

Таким образом, по-видимому, лишние аргументы просто тихо отбрасываются.

Теперь я хотел бы понять то, что действительно происходит здесь.

  1. Относительно законности: Если я понимаю ответ на Кастинг указателя функции к другому типу правильно, это - просто неопределенное поведение. Таким образом, то, что это выполняет и приводит к разумному результату, является просто чистой удачей, корректной? (или правильность со стороны разработчиков компилятора)
  2. Почему gcc не предупредит меня об этом, даже со Стеной? Это - что-то, что компилятор просто не может обнаружить? Почему?

Я происхожу из Java, где typechecking намного более строг, таким образом, это поведение смутило меня немного. Возможно, я страдаю от культурного шока :-).

10
задан Community 23 May 2017 в 12:19
поделиться

8 ответов

DELETE FROM A
FROM A INNER JOIN B ON A.P = B.P AND A.C = B.C
WHERE B.C = 1

Двойной от иногда бросает людей.

-121--4904072-

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

Тот факт, что этот звонок работал, является чистой удачей, основываясь на двух фактах:

  • Тип первого параметра одинаково для функции и указателя отливки. Если вы измените функцию, чтобы взять указатель на строку и попытаться распечатать эту строку, вы получите приятную сбой, поскольку код будет пытаться разымевать указатель на адресную память 2.
  • Конвенция о вызове, используемая по умолчанию, является для абонент для очистки стека. Если вы измените конвенцию о вызове, чтобы Callee очистил стек, вы получите абонент, нажав три параметра в стеке, а затем уборку Callee вверх (или достаточно пытаясь) один параметр. Это, скорее всего, приведет к повреждению стека.

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

15
ответ дан 3 December 2019 в 14:06
поделиться

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

12
ответ дан 3 December 2019 в 14:06
поделиться
  1. Да, это неопределенное поведение - все, что могло произойти, в том числе, в том числе «работа».

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

3
ответ дан 3 December 2019 в 14:06
поделиться

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

Вы можете легко столкнуться с различными указателями размера на встроенных платформах. Есть даже процессоры, где указатели данных и указатель функции обращаются к разным вещам (RAM для одного, ROM для другой), так называемая архитектура Гарварда. На X86 в реальном режиме вы можете иметь 16 бит и 32 бита. У WATCOM-C имел специальный режим для удлинителей DOS, где указатели данных были ширины 48 битов. Особенно с C следует знать, что не все POSIX, так как C может быть единственным языком, доступным на экзотическом оборудовании.

Некоторые компиляторы позволяют смешанным моделям памяти, где код гарантирован в течение 32 размеров битов, а данные адресуются с 64-битными указателями или общением.

Отредактируйте: Заключение, никогда не выделяйте указатель данных на указатель функции.

3
ответ дан 3 December 2019 в 14:06
поделиться

Функциональная Java имеет ZIP , Zipwith и Zipindex Как вы ожидаете от haskell или Scala. (Действительно, авторы в значительной степени все программисты Haskell.)

-121--1092646-

Поведение определяется Конвенцией о вызове. Если вы используете призванную конвенцию, в котором звонящий толкает и всплывает стек, то он будет работать нормально в этом случае, так как только это будет означать, что на стеке будет лишних байтов. У меня в данный момент у меня нет GCC, но с компилятором Microsoft этот код:

int ( __cdecl * fptr)(int,int,int) = (int (__cdecl * ) (int,int,int)) (ptr);

. Следующая сборка генерируется для вызова:

push        8
push        4
push        2
call        dword ptr [ebp-4]
add         esp,0Ch

обратите внимание на 12 байт (0CH), добавленные в стек после вызова. После этого стек в порядке (при условии, что Callee __CDECL в этом случае, чтобы он не пытался также очистить стек). Но со следующим кодом:

int ( __stdcall * fptr)(int,int,int) = (int (__stdcall * ) (int,int,int)) (ptr);

Add Add Esp, 0CH не генерируется в сборке. Если Callee находится __CDECL в этом случае, стек будет поврежден.

2
ответ дан 3 December 2019 в 14:06
поделиться
  1. . Andyly Я точно не знаю, но вы определенно не хотите воспользоваться поведением, если это удача или , если он является компилятором.

  2. Это не заслужено предупреждением, потому что актеры явный. Отказ, вы информируете компилятора, который вы знаете лучше. В частности, вы разбиваете void * , и как таковые вы говорите: «Возьмите адрес, представленный этим указателем, и сделать это так же, как этот другой указатель» - актерский состав просто информирует Компилятор, который вы уверены, что на целевом адресе, на самом деле, то же самое. Хотя здесь мы знаем, что это неверно.

1
ответ дан 3 December 2019 в 14:06
поделиться

Я должен освежить мою память о двоичной планировке Calling Convention в какой-то момент, но я уверен, что это то, что происходит:

  • 1: это не удача. C Conling Convensing Convence, и дополнительные данные в стеке не являются фактором для сайта вызова, хотя он может быть перезаписан CALLEE, так как Callee не знает об этом.
  • 2: «жесткий» отлитый, используя скобки, говорит компилятору, что вы знаете, что вы делаете. Поскольку все необходимые данные находятся в одном устройстве компиляции, компилятор может быть достаточно умным, чтобы выяснить, что это явно незаконно, но конструктор (ы) C не сосредоточился на уловленном угловом корпусе проверяемую ошибку. Проще говоря, компилятор доверяет, что вы знаете, что вы делаете (возможно, неразумно в случае многих программистов C / C ++!)
1
ответ дан 3 December 2019 в 14:06
поделиться

Чтобы ответить на ваши вопросы:

  1. Чистая удача - вы можете легко погрузить стек и перезаписать указатель возврата к следующему выполнению кода. Поскольку вы указали указатель функции с 3 параметрами, и вызывал указатель функции, остальные два параметра «отбрасываются» и, следовательно, поведение не определено. Представьте себе, если этот 2-й или 3-й параметр содержал бинарную инструкцию, и выскочил стек процедуры вызова ....

  2. Нет предупреждения, так как вы использовали void * , указатель и отливки. Это вполне законный код в глазах компилятора, даже если у вас явно указан -Wall коммутатор. Компилятор предполагает, что вы знаете, что вы делаете! Это секрет.

Надеюсь, это поможет, С уважением, Том.

0
ответ дан 3 December 2019 в 14:06
поделиться
Другие вопросы по тегам:

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