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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@Manoj Если вы изучите (или можете предоставить) листинг сборки для SetCallback, сгенерированный обоими компиляторами, мы сможем получить окончательный ответ.

Во-первых, утверждения Паскаля Кука верны, и Lindydancer показывает, как правильно установить обратный вызов. Мой ответ – это всего лишь попытка объяснить реальную проблему.

Я думаю, проблема связана с тем, что Linux и другая платформа используют разные 64-битные модели (см. 64-битные модели в Википедии ). Обратите внимание, что Linux использует LP64 (int 32 бит). Нам нужна более подробная информация на другой платформе. Если это SPARC64, он использует ILP64 (int is 64 bit).

Насколько я понимаю, проблема наблюдалась только в Linux и ушла, если вы указали локальную переменную int. Вы пробовали это с оптимизацией? Скорее всего, этот хак не будет иметь положительного эффекта при оптимизации.

В обеих 64-битных моделях указатели должны быть 64-битными, независимо от того, указывают ли они на код или данные. Однако, возможно, что это не так (например, сегментированные модели памяти); следовательно, предупреждения Паскаля и Линдиантера.

Если указатели имеют одинаковый размер, то остается вопрос о возможной проблеме выравнивания стека. Введение локального int (который 32 бит под Linux) может изменить выравнивание. Это будет иметь эффект, если void * и указатели функций имеют разные требования к выравниванию. Сомнительный сценарий.

Тем не менее, различные 64-битные модели памяти, скорее всего, являются причиной того, что вы наблюдали. Вы можете предоставить списки сборок, чтобы мы могли их проанализировать.

в отличие от других, да, вы можете иметь указатель 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)(); } 
  • Опциональный параметр закрытия с быстрой скоростью 3
  • C ++ наследует от нескольких базовых classов с тем же именем виртуальной функции
  • Проходит ли указатель указателя, передается по значению в C ++?
  • c ++ проблема с перегрузкой функции в унаследованном classе
  • Что такое гражданская функция первого classа?
  • Изменение адреса, содержащегося в указателе с использованием функции
  • Когда имя массива или имя функции «преобразуется» в указатель? (в С)
  • Как создать функцию шаблона внутри classа? (C ++)
  • Вызов метода с приемником указателя объектом вместо указателя на него?
  • Возврат указателя локальной переменной C ++
  • C - распределение матрицы в функции
  • Давайте будем гением компьютера.