Подтвердить что ты не робот

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));
}

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

4b9b3361

Ответ 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)();
}