Массив функций в C - PullRequest
       298

Массив функций в C

2 голосов
/ 16 ноября 2011

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

Вот как нумеруются функции:

enum 
  {
    SYS_HALT,                   /* Halt the operating system. */
    SYS_EXIT,                   /* Terminate this process. */
    SYS_EXEC,                   /* Start another process. */
    SYS_WAIT,                   /* Wait for a child process to die. */
    SYS_CREATE,                 /* Create a file. */
    SYS_REMOVE,                 /* Delete a file. */
    SYS_OPEN,                   /* Open a file. */
    SYS_FILESIZE,               /* Obtain a file's size. */
    SYS_READ,                   /* Read from a file. */
    SYS_WRITE,                  /* Write to a file. */
    SYS_SEEK,                   /* Change position in a file. */
    SYS_TELL,                   /* Report current position in a file. */
    SYS_CLOSE,                  /* Close a file. */
  };

А вот список прототипов функций:

void halt (void) NO_RETURN;
void exit (int status) NO_RETURN;
pid_t exec (const char *file);
int wait (pid_t);
bool create (const char *file, unsigned initial_size);
bool remove (const char *file);
int open (const char *file);
int filesize (int fd);
int read (int fd, void *buffer, unsigned length);
int write (int fd, const void *buffer, unsigned length);
void seek (int fd, unsigned position);
unsigned tell (int fd);
void close (int fd);

Основной вопрос

Есть ли простой способ поместить все функции в массив или другую структуру данных в C?

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

Спасибо

Редактировать: Что бы я хотел делать

Я хочу иметь возможность определить некоторый массив и сохранить указатель на функции в индексах порядковых номеров перечисления.

//I don't know how to handle the type here
<some_type> system_call_arr[128];

system_call_arr[SYS_HALT] = halt;
system_call_arr[SYS_EXIT] = exit;
system_call_arr[SYS_EXEC] = exec;

// and so on....

Редактировать 2

Поскольку Бен сказал, что все аргументы будут вписываться в 32-битный аргумент, я не могу определить что-то вроде этого:

typedef int (*sys_func) (uint32_t, uint32_t, uint32_t);
sys_func syscall_array[128];

syscall_array[SYS_HALT] = (sys_func)halt;
//etc....

Это что-то вроде хорошей практики?

Ответы [ 4 ]

2 голосов
/ 16 ноября 2011

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

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

2 голосов
/ 16 ноября 2011

В любой современной системе вы можете просто создать массив void * для хранения указателей на функции. Но это технически неопределенное поведение в ANSI C. Вместо этого вы можете использовать тип указателя универсальной функции, например void (*) (), и приводить указатель к правильному типу указателя на функцию, когда он вызывается См. FAQ по C *, Но вам все равно понадобится оператор switch для выполнения этого приведения, так как, как упоминает Иисус, вы можете просто вызвать правильную функцию одновременно и полностью забыть массив.

2 голосов
/ 16 ноября 2011

Классический способ UNIX сделать это - дать всем этим функциям одну и ту же сигнатуру (скажем, int sys_func(struct args *a), а затем поместить их все в массив функций. Когда вы собираете аргументы для вызовов, вы просто помещаете их как arg1, arg2 и т. Д. Все в struct args, и каждый системный вызов использует их по-разному. Или вы можете создать псевдоним специальной структуры поверх нее, чтобы дать им значимые имена.

В своем редактировании вы спрашиваете:что-то вроде этой хорошей практики? "Я дал свой ответ в контексте вашего вопроса, который, по-видимому, является реализацией простого ядра операционной системы. Операционные системы обманывают способами, которые никогда не должны делать обычные приложения. Он может использовать предположения о словеразмер (например, что все 32-битный, как в ILP-32 ) и знать что-то о ABI , потому что реализация этих вещей является частью работы операционной системы. Если ваша ОСпереносимый на другие платформы, он будет компилироваться в различных вариантах, например, syscall для обработки этих случаев.

ЗачемЯ предлагаю struct args, а не ваш метод?Потому что в операционной системе у вас будет несколько слоев между пользователем, выполняющим системный вызов, и реальным кодом, выполняющимся из таблицы указателей на функции.Если вы передадите эти аргументы явно через каждый слой, вы скопируете их несколько раз.Если первый слой может просто предоставить указатель, то он «перепрыгивает» аргументы через промежуточные функции.Если вы имеете дело с ABI, где соглашение о вызовах состоит в том, чтобы выдвигать аргументы в стеке, вы можете полностью избежать копирования аргументов и просто взять соответствующий адрес стека в качестве аргументов.

Вы можете найти обсуждения этогои почти все остальные аспекты проектирования операционной системы в Проектирование и реализация операционной системы 4.4 BSD , автор McKusick et.и др.Если вы можете найти более старую версию 4.3 , то это на самом деле довольно тонкий том и все еще полностью соответствует тому, над чем вы работаете.

1 голос
/ 16 ноября 2011

Если вы хотите отказаться от безопасности типов, вы можете создать тип объединения, например, так:

union Args {
    size_t st;
    ssize_t sst;
    int i;
    unsigned u;
    pid_t pt;
    mode_t mt;
    char *pc;
    const char* cpc;
    void *pv;
    const void *cpv;
    /* etc */
};

Затем, для каждой функции, пусть они примут union Args * в качестве списка параметров, иsize_t как число параметров (open может принять 3 параметра - он использует va_ * для проверки их по мере необходимости. Пусть они возвращают union Args.

Примечание: для функций voidимейте их return 0.

Например

union Args my_open(union Args *args, size_t nargs) {
    union Args a;
    switch (nargs) {
    case 2:
        a.i = open(args[0].cpc, args[1].i);
        break;
    case 3:
        a.i = open(args[0].cpc, args[1].i, args[2].mt);
        break;
    default:
         errno = EINVAL;
         a.i = -1;
    }
    return a;
}

Тогда вы можете иметь что-то вроде:

typedef union Args (*My_func)(union Args *, size_t);
My_func *fptrs;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...