Я только начинаю переносить голову вокруг указателей функции в 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
Таким образом, по-видимому, лишние аргументы просто тихо отбрасываются.
Теперь я хотел бы понять то, что действительно происходит здесь.
Я происхожу из Java, где typechecking намного более строг, таким образом, это поведение смутило меня немного. Возможно, я страдаю от культурного шока :-).
DELETE FROM A
FROM A INNER JOIN B ON A.P = B.P AND A.C = B.C
WHERE B.C = 1
Двойной от иногда бросает людей.
-121--4904072-Дополнительные параметры не отбрасываются. Они правильно размещены на стеке, как если бы вызов сделан в функцию, которая ожидает трех параметров. Однако, поскольку ваша функция заботится только о одном параметре, она выглядит только в верхней части стека и не касается других параметров.
Тот факт, что этот звонок работал, является чистой удачей, основываясь на двух фактах:
Нет никакого способа, который компилятор не может предупредить вас о потенциальных проблемах, подобных этому за одну простую причину - в общем случае, он не знает ценность указателя на время компиляции, поэтому он не может оценить, что он указывает на Отказ Представьте, что указатель функции указывает на метод в виртуальной таблице класса, созданную во время выполнения? Таким образом, вы говорите компилятору, это указатель на функцию с тремя параметрами, компилятор поверят вам.
Если вы берете машину и бросьте его как молоток, компилятор возьмет вас на ваше слово, что автомобиль - молоток, но это не превращает машину в молоток. Компилятор может быть успешным в использовании автомобиля, чтобы управлять гвоздем, но это зависимое от зависания. Это все еще неразумное дело.
Да, это неопределенное поведение - все, что могло произойти, в том числе, в том числе «работа».
Литые предотвращает выдачу компилятора предупреждение. Кроме того, компиляторы не требуются никаких требований к диагностике возможных причин неопределенного поведения. Причина этого состоит в том, что либо невозможно сделать это, или это было бы слишком сложно и / или привести к значительному накладным расходам.
Худшее преступление вашего отличия состоит в том, чтобы отбросить указатель данных на указатель функции. Это хуже, чем изменение подписи, потому что нет никаких гарантий, что размеры указателей функций и указатель данных равны. И вопреки теоретическим неопределенное поведение, этот можно встретить в дикой природе даже на передовых машинах (не только в встраиваемых системах).
Вы можете легко столкнуться с различными указателями размера на встроенных платформах. Есть даже процессоры, где указатели данных и указатель функции обращаются к разным вещам (RAM для одного, ROM для другой), так называемая архитектура Гарварда. На X86 в реальном режиме вы можете иметь 16 бит и 32 бита. У WATCOM-C имел специальный режим для удлинителей DOS, где указатели данных были ширины 48 битов. Особенно с C следует знать, что не все POSIX, так как C может быть единственным языком, доступным на экзотическом оборудовании.
Некоторые компиляторы позволяют смешанным моделям памяти, где код гарантирован в течение 32 размеров битов, а данные адресуются с 64-битными указателями или общением.
Отредактируйте: Заключение, никогда не выделяйте указатель данных на указатель функции.
Функциональная Java имеет ZIP
, Zipwith
и Zipindex
Как вы ожидаете от haskell или Scala. (Действительно, авторы в значительной степени все программисты Haskell.)
Поведение определяется Конвенцией о вызове. Если вы используете призванную конвенцию, в котором звонящий толкает и всплывает стек, то он будет работать нормально в этом случае, так как только это будет означать, что на стеке будет лишних байтов. У меня в данный момент у меня нет 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 в этом случае, стек будет поврежден.
. Andyly Я точно не знаю, но вы определенно не хотите воспользоваться поведением, если это удача или , если он является компилятором.
Это не заслужено предупреждением, потому что актеры явный. Отказ, вы информируете компилятора, который вы знаете лучше. В частности, вы разбиваете void *
, и как таковые вы говорите: «Возьмите адрес, представленный этим указателем, и сделать это так же, как этот другой указатель» - актерский состав просто информирует Компилятор, который вы уверены, что на целевом адресе, на самом деле, то же самое. Хотя здесь мы знаем, что это неверно.
Я должен освежить мою память о двоичной планировке Calling Convention в какой-то момент, но я уверен, что это то, что происходит:
Чтобы ответить на ваши вопросы:
Чистая удача - вы можете легко погрузить стек и перезаписать указатель возврата к следующему выполнению кода. Поскольку вы указали указатель функции с 3 параметрами, и вызывал указатель функции, остальные два параметра «отбрасываются» и, следовательно, поведение не определено. Представьте себе, если этот 2-й или 3-й параметр содержал бинарную инструкцию, и выскочил стек процедуры вызова ....
Нет предупреждения, так как вы использовали void *
, указатель и отливки. Это вполне законный код в глазах компилятора, даже если у вас явно указан -Wall
коммутатор. Компилятор предполагает, что вы знаете, что вы делаете! Это секрет.
Надеюсь, это поможет, С уважением, Том.