Приведение указателя на функцию C к пустому указателю - PullRequest
20 голосов
/ 07 апреля 2011

Я пытаюсь запустить следующую программу, но получаю странные ошибки:

Файл 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));
}

Пожалуйста, помогите с причиной проблемы. Спасибо.

Ответы [ 5 ]

35 голосов
/ 07 апреля 2011

Стандарт C не позволяет приводить указатели функций к void*. Вы можете приводить только к указателю на функцию другого типа. В стандарте C11, 6.3.2.3 §8 :

Указатель на функцию одного типа может быть преобразован в указатель на функция другого типа и обратно снова

Важно, что вы должны привести к исходному типу, прежде чем использовать указатель для вызова функции (технически, к совместимому типу. Определение «совместимый» в 6.2.7 ).

Обратите внимание, что стандарт POSIX, которому многие (но не все) компиляторы C тоже должны следовать из-за контекста, в котором они используются, требует, чтобы указатель функции мог быть преобразован в void* и обратно. Это необходимо для некоторых системных функций (например, dlsym).

14 голосов
/ 22 мая 2013

Стандарт, к сожалению, не допускает приведение между указателями данных и указателями функций (потому что это может не иметь смысла на некоторых очень непонятных платформах), даже если 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
9 голосов
/ 07 апреля 2011

У меня есть три правила, когда дело доходит до указателей данных и указателей кода:

  • Не не смешивать указатели данных и указатели кода
  • Не смешивать указатели данных и указатели кода
  • Не никогда не смешивайте указатели данных и указатели кода!

В следующей функции:

void setCallback(const void *fnPointer)
{
    gfnPtr = *((FN_GET_VAL*) (&fnPointer));
}

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

Попробуйте переписать это как:

void setCallback(FN_GET_VAL fnPointer)
{
     gfnPtr = fnPointer;
}

Кроме того, вы можете (или должны) сбросить приведение при установке указателя:

main()
{
   setCallback(myfunc);
   gfnPtr();
}

В качестве дополнительного бонуса вы теперь можете использовать обычные проверки типов, выполняемые компилятором.

2 голосов
/ 02 декабря 2012

Я предложу возможное частичное объяснение.

@ 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-битные модели памяти, скорее всего, являются причиной того, что вы наблюдали. Вы можете предоставить списки сборок, чтобы мы могли их проанализировать.

0 голосов
/ 16 октября 2016

в отличие от того, что говорят другие, да, вы можете иметь указатель 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)();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...