Можете ли вы привести «указатель на указатель на функцию» в void * - PullRequest
0 голосов
/ 28 октября 2018

Вдохновленный комментариями к моему ответу здесь .

Является ли эта последовательность шагов законной в стандарте C (C11)?

  1. Создайте массив указателей на функции
  2. Возьмите указатель на первую запись и приведите этот указатель на указатель на функцию в void*
  3. Выполните арифметику указателя для этого void*
  4. Приведите его обратно к указателю на указатель функции и разыменуйте его.

Или эквивалентно коду:

void foo(void) { ... }
void bar(void) { ... }

typedef void (*voidfunc)(void);
voidfunc array[] = {foo, bar}; // Step 1

void *ptr1 = array; // Step 2

void *ptr2 = (char*)ptr1 + sizeof(voidfunc); // Step 3

voidfunc bar_ptr = *(voidfunc*)ptr2; // Step 4

Я думалчто это будет разрешено, так как фактические указатели на функции доступны только через правильно типизированный указатель.Но Эндрю Хенле указал, что это, кажется, не охватывается Стандартным разделом 6.3.2.3: Указатели .

Ответы [ 3 ]

0 голосов
/ 28 октября 2018

Арифметика указателей на void* не на языке Си.Вы не делаете это, хотя, вы делаете арифметику указателя на char*, что совершенно нормально.Вы могли бы использовать char* вместо void* для начала.

Эндрю Хельн, кажется, упускает из виду тот факт, что указатель на функцию является объектом, а его тип является типом объекта.Это простой простой факт, а не что-то завуалированное в завесе тайны, как, по-видимому, подразумевают некоторые другие комментаторы.Таким образом, его возражение против приведения указателя на указатель на функцию является необоснованным, поскольку указатели на любой тип объекта могут быть приведены к void*.

Однако стандарт C, похоже, неразрешить использовать (T*)((char*)p + sizeof(T)) вместо (p+1) (где p - указатель на элемент массива типа T), или, по крайней мере, я не могу найти такое разрешение в тексте.Из-за этого ваш код может быть недопустимым.

0 голосов
/ 30 октября 2018

Да, код в порядке.Здесь есть различные подводные камни и правила конвертации:

  • C разбивает все типы на две основные категории: объекты и функции.Указатель на функцию - это скалярный тип , который, в свою очередь, является объектом.(C17 6.2.5)
  • void* - это общий тип указателя для указателей на тип объекта.Любой указатель на тип объекта может быть неявно преобразован в / из void*.(C17 6.3.2.3 §1).
  • Не существует такого общего типа указателя для указателей на тип функции.Таким образом, указатель функции не может быть преобразован в void* или наоборот.(C17 6.3.2.3 §1)
  • Однако любой тип указателя на функцию может быть преобразован в другой тип указателя на функцию и обратно, что позволяет нам использовать что-то вроде, например, void(*)(void) в качестве универсального типа указателя на функцию.Пока вы не вызываете функцию через неверный тип указателя на функцию, это нормально.(C17 6.3.2.3 §8)

Указатели на функции указывают на функции, но они сами по себе являются объектами, как и любой указатель.И поэтому вы можете использовать void* для указания на адрес указателя функции .

Следовательно, использование void* для указания на указатель на функцию подойдет.Но не использовать его, чтобы указывать непосредственно на функцию.В случае void *ptr1 = array; массив превращается в указатель на первый элемент, a void (**)(void) (эквивалентный voidfunc* в вашем примере).Вы можете указать на такой указатель на указатель на функцию с помощью void*.

Кроме того, относительно арифметики указателя:

  • Арифметика указателя на void* невозможна.(C17 6.3.2.2) Такая арифметика является распространенным нестандартным расширением, которого следует избегать.Вместо этого используйте указатель на тип символа.
  • Указатель на тип символа может, как особый случай, использоваться для перебора любого объекта (C17 6.2.3.3 §7).Помимо проблем, связанных с выравниванием, это четко определено и не нарушает «строгое указание псевдонима указателя», если вы отмените ссылку на указатель символа (C17 6.5 §7).

Следовательно, (char*)ptr1 + sizeof(voidfunc); тоже хорошо.Затем вы конвертируете из void* в voidfunc*, в voidfunc, который является исходным типом указателя функции, хранящимся в массиве.

Как отмечалось в комментариях, вы можете значительно улучшить читаемость этого кода, используяtypedef к типу функции:

typedef void (voidfunc)(void);

voidfunc* array[] = {&foo, &bar}; // Step 1
void* ptr1 = array; // Step 2
void* ptr2 = (char*)ptr1 + sizeof(voidfunc*); // Step 3
voidfunc* bar_ptr = *(voidfunc**)ptr2; // Step 4
0 голосов
/ 28 октября 2018

Ваш код правильный.

Указатель на функцию - это объект, и вы переводите указатель на объект (указатель на указатель на функцию) на voidуказатель и обратно;и, наконец, разыменование указателя на объект.

Что касается char арифметики указателя, на это ссылается сноска 106 C11:

106) Другой способ приблизиться к арифметике указателей - это сначала преобразовать указатель (и) в указатель (и) символа: в этой схеме целочисленное выражение, добавленное или вычтенное из преобразованного указателя, сначала умножается на размер объекта, на который изначально указывалосьи полученный указатель преобразуется обратно в исходный тип.Для вычитания указателя результат различия между символьными указателями аналогично делится на размер объекта, на который изначально указывался.При таком рассмотрении реализация должна предоставить только один дополнительный байт (который может перекрывать другой объект в программе) сразу после окончания объекта, чтобы удовлетворить требования «один за последним элементом».

...