C-образный указатель на функцию void pointer
Я пытаюсь запустить следующую программу, но получаю некоторые странные ошибки:
Файл 1.c:
typedef unsigned long (*FN_GET_VAL)(void);
FN_GET_VAL gfnPtr;
void setCallback(const void *fnPointer)
{
gfnPtr = *((FN_GET_VAL*) (&fnPointer));
}
Файл 2.c:
extern FN_GET_VAL gfnPtr;
unsigned long myfunc(void)
{
return 0;
}
main()
{
setCallback((void*)myfunc);
gfnPtr(); /* Crashing as value was not properly
assigned in setCallback function */
}
Здесь gfnPtr() разбивается на 64-битный suse linux при компиляции с gcc. Но он успешно вызывает gfnPtr() VC6 и SunOS.
Но если я изменю функцию, указанную ниже, она успешно работает.
void setCallback(const void *fnPointer)
{
int i; // put any statement here
gfnPtr = *((FN_GET_VAL*) (&fnPointer));
}
Пожалуйста, помогите с причиной проблемы. Спасибо.
Ответы
Ответ 1
Стандарт C не позволяет приводить указатели на функции к void*
. Вы можете приводить только к другому типу указателя на функцию. В стандарте C11, 6.3.2.3 §8:
Указатель на функцию одного типа может быть преобразован в указатель на функцию другого типа и обратно
Важно, что вы должны привести обратно к исходному типу, прежде чем использовать указатель для вызова функции (технически, к совместимому типу. Определение "совместимый" в 6.2.7).
Обратите внимание, что стандарт POSIX, которому должны следовать многие (но не все) компиляторы C из-за контекста, в котором они используются, требует, чтобы указатель функции мог быть преобразован в void*
и обратно. Это необходимо для некоторых системных функций (например, dlsym
).
Ответ 2
В стандарте, к сожалению, не допускается кастинг между указателями данных и указателями функций (поскольку это может не иметь смысла на некоторых действительно неясных платформах), хотя POSIX и другие требуют таких приведений. Один способ обхода - не указывать указатель, а указывать указатель на указатель (это все в порядке с помощью компилятора, и он выполнит работу на всех нормальных платформах).
typedef void (*FPtr)(void); // Hide the ugliness
FPtr f = someFunc; // Function pointer to convert
void* ptr = *(void**)(&f); // Data pointer
FPtr f2 = *(FPtr*)(&ptr); // Function pointer restored
Ответ 3
У меня есть три эмпирических правила, когда речь идет о указателях данных и указателях кода:
- Не смешивайте указатели данных и указатели кода
- Не смешивайте указатели данных и указатели кода
- Никогда не смешивайте указатели данных и указатели кода!
В следующей функции:
void setCallback(const void *fnPointer)
{
gfnPtr = *((FN_GET_VAL*) (&fnPointer));
}
У вас есть указатель данных, в котором вы указываете на указатель функции. (Не говоря уже о том, что вы делаете это, сначала беря адрес самого указателя, бросаете его указателю на указатель, прежде чем де-ссылку на него).
Попробуйте переписать его как:
void setCallback(FN_GET_VAL fnPointer)
{
gfnPtr = fnPointer;
}
Кроме того, вы можете (или должны) отказаться от приведения при установке указателя:
main()
{
setCallback(myfunc);
gfnPtr();
}
В качестве дополнительного бонуса теперь вы можете использовать обычные проверки типов, выполняемые компилятором.
Ответ 4
Я предложу возможное частичное объяснение.
@Manoj Если вы изучите (или можете предоставить) листинг сборки для SetCallback, сгенерированный обоими компиляторами, мы можем получить окончательный ответ.
Во-первых, утверждения Pascal Couq верны, и Lindydancer показывает, как правильно установить обратный вызов. Мой ответ - это только попытка объяснить реальную проблему.
Я думаю, проблема связана с тем, что Linux и другая платформа используют разные 64-битные модели (см. 64-битные модели в Википедии). Обратите внимание, что Linux использует LP64 (int 32 бит). Нам нужна более подробная информация на другой платформе. Если это SPARC64, он использует ILP64 (int - 64 бит).
Как я понимаю, проблема наблюдалась только в Linux, и ушла, если вы указали локальную переменную int. Вы пробовали это с оптимизацией? Скорее всего, этот хак не окажет положительного эффекта при оптимизации.
В обеих 64-битных моделях указатели должны быть 64-битными, независимо от того, указывают ли они на код или данные. Однако возможно, что это не так (например, сегментированные модели памяти); следовательно, предупреждения Паскаля и Линдсиантера.
Если указатели имеют одинаковый размер, то остается проблема возможного выравнивания стека. Введение локального int (который 32 бит под Linux) может изменить выравнивание. Это будет иметь эффект, если void * и указатели функций имеют разные требования к выравниванию. Сомнительный сценарий.
Тем не менее, различные 64-битные модели памяти, скорее всего, являются причиной того, что вы наблюдали. Вы можете предоставить списки сборок, чтобы мы могли их проанализировать.
Ответ 5
в отличие от других, да, вы можете иметь указатель void*
как указатель на функцию, но семантика очень сложна в использовании.
Как вы можете видеть, вам НЕ НУЖНО использовать его как void*
, просто назначьте его как обычно. Я запускал ваш код, как это, и я отредактировал его для работы.
file1.c:
typedef unsigned long (*FN_GET_VAL)(void);
extern FN_GET_VAL gfnPtr;
void setCallback(const void *fnPointer)
{
gfnPtr = ((FN_GET_VAL) fnPointer);
}
file2.c:
int main(void)
{
setCallback(myfunc);
(( unsigned long(*)(void) )gfnPtr)();
}