Эмулирование приложения частичной функции в c

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

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

// example, not actual code
void (*atJoystickLeft)(void); // fixed signature

.. code ..
atJoystickLeft = &func1;
.. code ..

void func1(void) { func2(10, "a string"); } /

Я хотел бы вместо этого:

atJoystickLeft = &func2(10, "a string");

поэтому я могу избавиться от функции func1, которая предназначена только для установки параметра для вызова функции func2.

Это сделает код хотя бы на 20-30% меньше. Мой простой вопрос: у кого-то есть трюк для эмуляции приложения частичной функции в C? (С99)

Ответ 1

Короткий ответ: нет, C не поддерживает его.

Вы могли бы взломать переменный интерфейс, который взял адрес функции и создал фрейм стека для вызова этой функции с этими аргументами, но он не был бы переносимым 1. Если вы хотите сделать его переносимым и можете изменять другие функции, которые вы собираетесь вызывать через этот интерфейс (в форме, которая не подходит для прямого вызова), вы можете переписать все из них, чтобы получить va_alist как их единственный параметр и получить правильный номер/тип параметров с помощью va_arg:

// pointer to function taking a va_alist and return an int:
typedef int (*func)(va_alist); 

void invoke(func, ...) {
    va_alist args;
    va_start(args, func);
    func(args);
    va_end(args);
}

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

struct saved_invocation { 
     func f;
     argument_list args;
};

saved_invocation save(func f, ...) { 
    saved_invocation s;
    va_alist args;
    s.f = f;
    va_start(args, f);
    va_make_copy(s.args, args);
    va_end(args);
}

int invoke(saved_invocation const *s) { 
    s->f(s->args);
}

Для va_make_copy вы попадете в более нестандартные материалы. Это не будет то же самое, что va_copy - va_copy обычно просто сохраняет указатель на начало аргументов, который остается действительным только до тех пор, пока не вернется текущая функция. Для va_make_copy вам нужно будет сохранить все фактические аргументы, чтобы вы могли их восстановить позже. Предполагая, что вы использовали структуру argument, описанную ниже, вы можете пройти через аргументы с помощью va_arg и использовать malloc (или что-то другое) для выделения node для каждого аргумента и создания каких-то динамических данных (например, связанный список, динамический массив), чтобы удерживать аргументы, пока вы не будете готовы их использовать.

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

[end edit]

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

int function(va_alist args);

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

int adder(va_alist args) { 
    int arg1 = va_arg(args, int);
    int arg2 = va_arg(args, int);

    return arg1 + arg2;
}

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

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

struct argument { 
    enum type {CHAR, SHORT, INT, LONG, UCHAR, USHORT, UINT, ULONG, /* ... */ };
    union data { 
        char char_data;
        short short_data;
        int int_data;
        long long_data;
        /* ... */
    }
}

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

invoke(func, arg1, arg2);

... вы получите что-то вроде:

invoke(func, make_int_arg(arg1), make_long_arg(arg2));

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

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


  • "Не было бы портативным", это почти преуменьшение - вы действительно не можете сделать это в C вообще - вам просто нужно использовать язык ассемблера, даже начинать.

Ответ 2

Я не знаю ни одного прямого способа сделать то, что вы хотите, так как C не поддерживает закрытие или любой другой способ передать "скрытые параметры" вашим функциям (без использования глобальных или статических переменных, стиль strtok)

Вы, кажется, исходите из фона FP. В C все должно быть сделано вручную, и я бы рискнул сказать, что в этом случае лучше попытаться моделировать вещи в стиле OO: превратить ваши функции в "методы" , сделав их получите дополнительный аргумент, указатель на структуру "внутренних атрибутов", this или self. (Если вы знаете Python, это должно быть очень ясно:))

В этом случае, например, func1 может превратиться в нечто вроде этого:

typedef struct {
     int code;
     char * name;
} Joystick;

Joystick* makeJoystick(int code, char* name){
    //The constructor
    Joystick* j = malloc(sizeof Joystick);
    j->code = code;
    j->name = name;
    return j;
}

void freeJoystick(Joystick* self){
    //Ever noticed how most languages with closures/objects
    // have garbage collection? :)
}

void func1(Joystick* self){
    func2(self->code, self->name);
}

int main(){
     Joystick* joyleft  = make_Joystick(10, "a string");
     Joystick* joyright = make_Joystick(17, "b string");

     func1(joyleft);
     func1(joyright);

     freeJoystick(joyleft);
     freeJoystick(joyright);

     return 0;
}