Каррирование/связывание с ISO C99

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

double integrate(double (*f)(double), double lower, double upper, double step));

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

double func(double x, double z);

в отношении x. Я не могу передать func в integrate, так как у него неправильная подпись. Теперь я знаю следующие обходные пути, которые мы использовали, когда проходили курс численного анализа:

  • Используйте C++

    Я только что использовал C++ и ist std::bind для создания функтора (функционального объекта), который я мог передать процедуре интеграции. Теперь я бы просто использовал лямбда-функции, чтобы сделать это.

  • Использовать расширение GCC для функций в функции

    С GCC вы можете объявить функцию в функции. Таким образом, можно было бы сделать

    // z is set to some value in this function scope here.
    double inner(double x) {
        return func(x, z);
    }
    

    и передайте это inner функции integrate. Это нестандартно и не очень хорошо.

  • Использовать глобальные переменные

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

Существует ли способ сделать это на простом C, не сломав что-то?


person Martin Ueding    schedule 24.05.2014    source источник
comment
ISO C98 не существует. Первоначальным стандартом был ANSI C89; он был принят без существенных изменений как ISO C90. Более поздними стандартами были ISO C99 и ISO C11.   -  person Keith Thompson    schedule 25.05.2014
comment
все, что вы можете сделать с глобальным, вы можете сделать без глобального ...   -  person Grady Player    schedule 25.05.2014
comment
@KeithThompson Да, я имел в виду C99. Вероятно, я думал о C++98, когда писал это.   -  person Martin Ueding    schedule 26.05.2014


Ответы (5)


Одним из распространенных решений этой проблемы является изменение дизайна следующим образом:

double integrate(double (*f)(double, void*), void*,
                      double lower, double upper, double step);

Здесь вы передаете дополнительный void * в integrate, а он передается обратно в f. Это можно использовать для передачи произвольных данных, в вашем случае вы передадите указатель на z, а внутри функции f вы вернете указатель к исходному типу и восстановите значение z. Этот шаблон повсеместно используется в библиотеках C, например, вот прототип pthread_create< /а>:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                      void *(*start_routine) (void *), void *arg);
person pentadecagon    schedule 24.05.2014

Нет, C не может сделать это на уровне функций C/указателей функций C. Лучший способ сделать это для конкретного приложения (например, математических функций и интеграции) — использовать собственные структуры для представления математических функций и любых уже привязанных слотов переменных. Я бы, вероятно, не заставлял функции принимать различное количество аргументов, а вместо этого использовал бы указатель на массив аргументов; это значительно упрощает программный вызов их различными способами.

В качестве альтернативы вы можете использовать что-то вроде libffi, которое может выполнять такие привязки/закрытия, но оно определенно не переносимо и неэффективно.

person R.. GitHub STOP HELPING ICE    schedule 24.05.2014

Ваша проблема также может быть решена с помощью функций с переменными аргументами (и немного больше усилий):

#include <stdarg.h>

double integrate(double (*f)(double, ...), double lower, double upper, double step)
{
    return (f)(lower, upper);
}

double func1(double x, ...)
{
    va_list ap;
    double ret;

    va_start(ap, x);

    ret = x * va_arg(ap, double);

    va_end(ap);

    return ret;
}

double func2(double x, ...)
{
    return x;
}

Хотя не уверен, что я должен считать это чище...

person mfro    schedule 25.05.2014

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

double inner(double x) {
    return func(x, 100);
}

Это совершенно верно C89.

person luser droog    schedule 25.05.2014
comment
Конечно, недостатком является то, что вам нужно создать отдельную версию inner в исходном коде для каждого значения z. - person Samuel Edwin Ward; 25.05.2014
comment
Конечно, но я хотел сгенерировать новую функцию для множества разных z. Я обновил вопрос соответственно. - person Martin Ueding; 26.05.2014
comment
Также недостатком является то, что GCC прекратил поддержку этой функции, и на нее полагается код одного аспиранта; вынуждая его оставаться на более старой версии GCC до конца своей диссертации. - person Martin Ueding; 25.03.2018

Я на 99% уверен, что это невозможно с чистым C89. Для этого вам нужно создать новый указатель функции во время выполнения. Есть два способа получить указатель на функцию: из выражения функции или из стандартной библиотечной функции, которая возвращает указатель на функцию. Функциональные выражения относятся к функциям, определенным во время компиляции, так что это не сработает. Единственная стандартная библиотечная функция, которая возвращает указатель на функцию, — это signal, и это бесполезно, потому что вы получаете от нее только то, что вставляете.

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

Некоторое время я думал, что вы сможете добиться чего-то с setjmp и longjmp, но это просто заменяет проблему хранения double проблемой хранения jmp_buf.

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

/* platform-specific include */
#include <sys/mman.h>
/* standard includes */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

double func(double x, double z)
{
    printf("%g\n", z);
    return x;
}

double bar(double x)
{
    printf("y\n");
    return x;
}

double call(double (*f)(double))
{
    return f(0.0);
}

struct cDblDblRetDbl
{
    double (*function)(double, double);
    double a;
    char code[1];
    double pad;
};

static double helper(double x)
{
    /* Casting a function pointer to an object pointer is
     * not provided by the standard.
     * In addition, this only works because the compiler
     * happens to use RIP-relative addressing, so "helper"
     * points to the beginning of the currently executing
     * function, which is actually a copy of the one in
     * the object file.
     * It's worth noting explicitly that nothing in the
     * C standard says that a pointer to a function will
     * point to its machine code.
     */
    double *dp = (double *) helper;
    struct cDblDblRetDbl *data;
    /* Modify it to point after the structure.
     * This depends on the alignment and padding of the
     * structure, which is not portable.
     */
    dp += 2;
    data = (struct cDblDblRetDbl *) dp;
    /* back it up to the structure */
    --data;
    /* now call the function with the saved data. */
    return data->function(x, data->a);
}

/* There is no way to get the code size of a function,
 * so this is very nonportable.
 * I found it by examining the object file.
 */
#define CODE_SIZE 0x60

double (*curryDoubleDoubleReturningDouble(double (*function)(double, double), double a))(double)
{
    size_t size = sizeof(struct cDblDblRetDbl) + CODE_SIZE;
    /* valloc is nonstandard but we need an area aligned to a
     * page boundary for mprotect.
     */
    struct cDblDblRetDbl *result = valloc(size);
    result->function = function;
    result->a = a;
    /* Copy the code of the helper function into the structure.
     * Once again, we're casting a function pointer to an
     * object pointer and the standard doesn't say you can do that. 
     */
    memcpy(result->code, (void *) helper, CODE_SIZE);
    /* Memory protection is out of the scope of the standard,
     * and in a real program we need to check the return value.
     */
    mprotect(result, CODE_SIZE, PROT_READ | PROT_EXEC | PROT_WRITE);
    /* Casting an object pointer to a function pointer is also
     * not provided by the standard.
     * This example leaks memory.
     */
    return (double(*)(double)) result->code;
}

int main()
{
    call(bar);
    call(curryDoubleDoubleReturningDouble(func, 5));
    call(curryDoubleDoubleReturningDouble(func, 7));
    call(curryDoubleDoubleReturningDouble(func, 42.9));
}

Если вы написали helper на ассемблере и создали версии curryDoubleDoubleReturningDouble для конкретной ОС, вы, вероятно, могли бы заставить это работать во многих местах. Но я уверен, что есть компьютеры, на которых работает С, где вы не можете этого сделать.

person Samuel Edwin Ward    schedule 24.05.2014
comment
Законно преобразовать указатель функции одного типа в указатель функции любого другого типа и обратно, если вы не пытаетесь вызвать его из преобразованного типа, поэтому вы можете использовать любой тип указателя функции в качестве указателя функции. типа void *. Однако вам придется преобразовать его в правильный тип, прежде чем вызывать его, поэтому вам придется где-то хранить информацию для этого (возможно, с помощью другого параметра для этой цели), так что это не все такое элегантное решение. - person Crowman; 25.05.2014
comment
jmpbuf определяется как тип массива, поэтому вы не можете перемещать его значение с помощью присваиваний. Но вы можете memcpy это сделать. - person luser droog; 25.05.2014
comment
@PaulGriffiths, это правда, но я не думаю, что это даст вам новый указатель на функцию. - person Samuel Edwin Ward; 25.05.2014
comment
@luserdroog, хорошо, но куда я его положу? А почему бы не пропустить посредника и не поставить там double? - person Samuel Edwin Ward; 25.05.2014