Как успокоить компилятор C относительно указателя на функцию, принимающего любое количество аргументов? - PullRequest
5 голосов
/ 23 июля 2011

У меня есть указатель на функцию внутри struct, которая динамически устанавливается во время выполнения на адрес другой функции в различных местах моего кода. Это определено в моем заголовочном файле следующим образом:

    void *(*run)();

Во время компиляции я получаю следующее предупреждение об этом:

    warning: function declaration isn't a prototype

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

Если я изменю это на:

    void *(*run)(void);

Я получаю ошибки компиляции везде, где я его использую, потому что различные функции, использующие указатель, имеют разное количество аргументов, а высказывание void внутри скобок говорит компилятору, что он не принимает аргументов.

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

Я могу отключить предупреждение, добавив это к моим флагам компилятора:

    -Wno-strict-prototypes

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

Итак, мой вопрос: Как мне записать этот указатель функции в коде таким образом, чтобы компилятор удовлетворялся тем фактом, что он принимает любое количество аргументов любого типа?

Код работает отлично. Я просто хочу, чтобы предупреждение исчезло.

Ответы [ 5 ]

5 голосов
/ 23 июля 2011

Сохранить указатель как void * и привести к соответствующему типу указателя на функцию при необходимости? Имейте в виду, что не обязательно безопасно вызывать один тип указателя на функцию, как если бы это был другой тип, поэтому предупреждение, с которым вы начинаете, не совсем недействительно.

Вы можете привести указатель на функцию следующим образом:

void *genericPointer = ...;
void (*fp)(int, int) = genericPointer;
fp(123, 456);

Обратите внимание, что:

  • Здесь не требуется явное приведение, поскольку void * всегда может быть приведено к любому типу указателя.
  • Начальный «void» перед (*fp) является типом возврата указателя функции.
2 голосов
/ 23 июля 2011

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

* 1005 Е.Г. *

void (*g)();
void f()
{
    float x = 0.5;
    g(x); // double passed
}

Это означает, что вам нужно знать, что функция, которую вы на самом деле вызываете, имеет подпись, совместимую с аргументами, которые вы передаете после повышения.

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

Обратите внимание, что когда вы определяете свои функции с помощью прототипов, когда вы назначаете указатель на свою функцию на указатель на функцию без прототипа, вы эффективно преобразуете, скажем, void(*)(int, int) в void(*)(), так что это совершенно правильно и желательно выполнить обратное преобразование перед вызовом функции. gcc разрешает оба этих преобразования без предупреждения.

* 1016 Е.Г. *

void PerformCall( void(*p)() )
{
    if (some_condition)
    {
        // due to extra knowledge I now know p takes two int arguments
        // so use a function pointer with the correct prototype.
        void(*prototyped_p)(int, int) = p;
        prototyped_p( 3, 4 );
    }
}
2 голосов
/ 23 июля 2011

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

Но если я хорошо помню, что-то подобное, возможно, использовалось также в ядре Linux (?). Решение состоит в том, чтобы иметь общий указатель (например, тот, который у вас есть), и каждый раз, когда вы вызываете конкретную функцию, вы просто вводите ее в указатель для функции с конкретными аргументами. Возможно, вам придется ввести его, чтобы сначала аннулировать *, чтобы снова отключить компилятор: -)

0 голосов
/ 23 июля 2011

Если известны различные сигнатуры функций, используйте union. В противном случае используйте указатель типа void (*)(void) (фактически, любой тип указателя на функцию), чтобы удерживать общий указатель и преобразовывать его в правильный тип при установке значения и вызове кода.

Пример использования union:

union run_fn
{
    void *(*as_unary)(int);
    void *(*as_binary)(int, int);
};

struct foo
{
    union run_fn run;
};

void *bar(int, int);
struct foo foo;

foo.run.as_binary = bar;
void *baz = foo.run.as_binary(42, -1);

Пример использования явных приведений:

struct foo
{
    void (*run)(void);
};

void *bar(int, int);
struct foo foo;

foo.run = (void *(*)(int, int))bar;
void *baz = ((void *(*)(int, int))foo.run)(42, -1);

Не используйте void * для хранения указателей на функции - такое преобразование не определено стандартом ISO C и может быть недоступно на некоторых архитектурах.

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

0 голосов
/ 23 июля 2011

Попробуйте ввести определение указателя на функцию, а затем вызовите ее вызывающим:

typedef void *(*run)();

//when calling...
void my_foo() {}

run r = (run)my_foo;
...