Что, если таковые имеются, является технической причиной для указания аргумента указателя на функцию, а затем аргументов для этой функции или наоборот? - PullRequest
3 голосов
/ 14 апреля 2020

Возможно, основанный глубже на VLA, отладчиках, _Geneirc_, некоторых предлагаемых в настоящее время особенностях следующего C или некотором другом углу C, вопрос:

Есть ли преимущество кодирования (объективная причина), которое предпочитает одну сигнатуру функции другой, когда задействованы указатели на функции?

Я не нашел ни одного.


Как правило, порядок аргументов в сигнатуре функции - это просто выбор стиля. Однако я ищу причину, по которой в функции, отличной от (...) с аргументом указателя функции, может существовать преимущество для определенного порядка.

Я мог бы определить функцию, содержащую указатель функции, как

// Function pointer before its arguments a1,b1 it will eventually use
void foo1(void (*fun)(int a0, double b0), int a1, double b1) {
  fun(a1, b1); 
  fun(a1,-b1);
} 

или

// Function pointer after the a2,b2 arguments
void foo2(int a2, double b2, void (*fun)(int a0, double b0)) {
  fun(a2, b2); 
  fun(a2, -b2);
} 

Исследование стандартной библиотеки C предлагает 2 противоположных примера.

// Function pointer before `context`.
errno_t qsort_s(void *base, rsize_t nmemb, rsize_t size,
    int (*compar)(const void *x, const void *y, void *context), void *context); 

// Function pointer last.
void (*signal(int sig, void (*func)(int)))(int);

Пока что это, безусловно, выбор стиля.


Порядок важен с VLA

Я рассмотрел VLA s, где нужны аргументы перед arr2 и каким-то образом может подпись fun2() основываться на row2, col2, arr2 и получить некоторую выгоду. Это дало бы преимущество для указателя функции на след.

int foo2(int row2, int col2, char arr2[row2][col2], void (*fun2)(TBD_Signature);

Но я не нашел полезного примера.


[Редактировать] Возможно, другой способ взглянуть на один аспект из этого вопроса:

Может ли подпись указателя функции быть полезной из предыдущих аргументов функции?

 int bar(int some_arg, other_args, 
     (*f)(signature based on some_arg, other_args or their type));

Ответы [ 2 ]

0 голосов
/ 14 апреля 2020

Вы исключаете функции varargs, но я не уверен почему. Кроме того, я не уверен, что вы хотели исключить их в качестве подробностей реализации или теории.

Независимо от этого, одной из причин была бы производительность. Если ваше соглашение о вызовах помещает аргументы в стек справа налево, то размещение параметра указателя функции слева может оставить аргументы функции, уже находящейся в стеке, в правильном порядке:

int call_fn(int (*fn)(void * ignored, A_TYPE a, B_TYPE b), A_TYPE a, B_TYPE b);

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

0 голосов
/ 14 апреля 2020

Сомневаюсь, что есть какая-то техническая причина. Скорее, существует очень хорошая причина семантики c.

Когда объект концептуально «присоединяется» к другому посредством присваивания или, в случае указателей на функции, вызова, тогда мы склонны поставить этот объект после того, к которому он прикреплен. Например, при использовании структуры данных карты вам нужна функция, чтобы связать значение с ключом: вы «привязываете» это значение к ключу. Эта функция почти наверняка примет первый ключевой параметр, а второй параметр значения. При определении функции такой же порядок наблюдается в самом языке: имя функции предшествует именам ее параметров, потому что параметры являются «приложениями» к функции, а не наоборот.

В случае qsort_s указатель функции стоит первым: compar предшествует context. Здесь указатель на функцию является функтором в том смысле, что он передается как некоторый объект (указатель) в основную функцию (qsort_s), которая может вызывать ее сразу. Его аргументы (context) следуют после, потому что они являются вложениями к функтору.

В случае signal (также sigaction) указатель на функцию идет последним: func следует после sig. Здесь указатель функции - это обработчик или обратный вызов, сохраненный для дальнейшего использования. Это не вызывается напрямую; вместо этого он «назначается» или «присоединяется» к какому-либо объекту события (здесь представленному как sig, номер сигнала) и, таким образом, идет после него, даже если он затем вызывается с этим объектом события в качестве аргумента. Это похоже на функцию map, связывающую значение с ключом: значение является обработчиком, а ключ - событием.

В конкретном случае qsort_s, compar получает переданные значения из base также, но base сам по себе не является аргументом для compar.
Это соответствует третьему случаю, когда указатель на функцию идет последним, но предназначен для воздействия на части предыдущих аргументов. Это часто происходит в обобщенных c алгоритмах, которые требуют какого-либо функтора, часто предиката, для воздействия на данные (стандартная библиотека C ++ полна таких примеров). В этом случае данные идут первыми, хотя указатель на функцию вызывается с отдельными частями этих данных. Концептуально, функтор привязывается к данным, чтобы воздействовать на них по частям.
Обычная старая qsort (без параметра context) также подходит для этого случая.

Что касается вашего последнего вопроса ( Может ли сигнатура указателя функции извлекать полезную информацию из предыдущих аргументов функции? ), я бы сказал, нет.
Конечно, она не может изменяться в зависимости от значений аргументов во время выполнения. , поскольку подпись - вещь времени компиляции (по крайней мере, в C). Вы всегда можете иметь void* параметры и изменять их интерпретацию на основе значений времени выполнения, но это не одно и то же.
Я не думаю, что это может измениться в зависимости от типов других аргументов, по крайней мере, не в C. Другие языки имеют механизмы, которые делают это возможным, но они обычно сложны и в любом случае не стоят этого.

...