Синхронизация индексов таблицы указателей функций с содержимым таблицы - PullRequest
2 голосов
/ 12 мая 2010

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

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

Пример:

(export_table.c)

// Assume each function in the table has an associated declaration
typedef void (*Function_Ptr)(void);

Function_Ptr    Export_Function_Table[] =
{
  0,
  Print,
  Read,
  Write,
  Process,
};

Вот заголовочный файл:
export_table.h

#define ID_PRINT_FUNCTION 1
#define ID_READ_FUNCTION  2
#define ID_WRITE_FUNCTION 3
#define ID_PROCESS_FUNCTION 4

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

Ответы [ 6 ]

3 голосов
/ 13 мая 2010

Используя C99, вы можете использовать назначенные инициализаторы:

enum {
    ID_PRINT_FUNCTION   = 1,
    ID_READ_FUNCTION    = 2,
    ID_WRITE_FUNCTION   = 3,
    ID_PROCESS_FUNCTION = 4,
};

(Завершающая запятая разрешена в C99; технически это не в C90.)

// Assume each function in the table has an associated declaration
typedef void (*Function_Ptr)(void);

Function_Ptr Export_Function_Table[] =
{
    [ID_READ_FUNCTION]    = Read,
    [ID_WRITE_FUNCTION]   = Write,
    [ID_PROCESS_FUNCTION] = Process,
    [ID_PRINT_FUNCTION]   = Print,
};

Обратите внимание, что я намеренно изменил порядок - и компилятор разберется с этим. Кроме того, хотя я переписал значения '#define' в значения 'enum', он будет работать с любым из них.

Обратите внимание, что MSVC в Windows не поддерживает AFAIK. Это означает, что я не могу использовать его в коде, который должен быть перенесен между Linux и Windows - к моему большому раздражению.

1 голос
/ 12 мая 2010

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

1 голос
/ 12 мая 2010

Мой совет: не используйте C напрямую. Создайте файлы .c и .h из входного файла, записанного в некоторый локально определенный DSL , как часть вашего процесса сборки. Тогда у вас будет только один исходный файл для поддержки (записанный в вашем DSL), и компилятор DSL гарантирует, что экспортированные индексы соответствуют реализации массива.

Мы используем эту технику здесь. DSL представляет собой аннотированный C-файл, который выглядит примерно так:

@@generate .h
#ifndef __HEADER_H
#define __HEADER_H

@export FN_LIST

#endif
@@generate .c
#include "foo.h"

@define FN_LIST
int myArray[] = 
{
    @FN_INDEX_FOO
    12,
    @FN_INDEX_BAR
    13,
    @FN_INDEX_BAZ
    14,
}

, который генерирует foo.h, который выглядит как:

#ifndef __HEADER_H
#define __HEADER_H

#define FN_INDEX_FOO 0
#define FN_INDEX_BAR 1
#define FN_INDEX_BAZ 2

#endif

и foo.c, который выглядит как:

#include "foo.h"

int myArray[] = 
{
    /* FN_INDEX_FOO = 0 */
    12,
    /* FN_INDEX_BAR = 1 */
    13,
    /* FN_INDEX_BAZ = 2 */
    14,
}

У синтаксического анализатора есть возможность подсчитывать вложенность фигурных скобок и запятые для расчета индекса каждого элемента в массиве C.

1 голос
/ 12 мая 2010

Вместо массива вы можете определить структуру с именованными элементами для каждого указателя функции:

struct call_table {
    Function_Ptr reserved;    
    Function_Ptr print_fcn;    
    Function_Ptr read_fcn;    
    Function_Ptr write_fcn;    
    Function_Ptr process_fcn;    
};
0 голосов
/ 25 февраля 2016

Немногие программисты на Си пользуются преимуществами #undef, специально переопределяя макрос для ваших текущих целей. Это позволяет вам настроить все ваши данные в одной читаемой таблице, которую можно обновлять и изменять в 1 месте.

#define FUNCTION_MAP { \
   MAP(ID_NOP_FUNCTION,     NULL), \
   MAP(ID_PRINT_FUNCTION,   Print), \
   MAP(ID_READ_FUNCTION,    Read), \
   MAP(ID_WRITE_FUNCTION,   Write), \
   MAP(ID_PROCESS_FUNCTION, Process), \
}

#define MAP(x,y) x
enum function_enums FUNCTION_MAP;
#undef MAP

#define MAP(x,y) y
Function_Ptr Export_Function_Table[] = FUNCTION_MAP;
#undef MAP

но подождите, это еще не все из-за низкой и низкой цены в $ 0 с бесплатным S & H, вы можете выполнять все свои функции header Mumbo Jumbo все в одном месте.

#define FUNCTION_MAP  \
   /*   ENUM              Function, return, Arguments ...                  */ \
   MAP(ID_PRINT_FUNCTION,   Print,  int,    char *fmt, va_list *ap          ) \
   MAP(ID_READ_FUNCTION,    Read,   int,    int fd,    char *buf, size_t len) \
   MAP(ID_WRITE_FUNCTION,   Write,  int,    int fd,    char *buf, size_t len) \
   MAP(ID_PROCESS_FUNCTION, Process,int,    int                             )

//function enums
#define MAP(x,y,...) x,
enum function_enums { FUNCTION_MAP };
#undef MAP

//function pre-definitions with unspecified number of args for function table
#define MAP(x,fn_name,ret,...) ret fn_name();
FUNCTION_MAP
#undef MAP

//function tables with unspecified number of arguments
#define MAP(x,y,...) y,
typedef int (*Function_Ptr)();
Function_Ptr Export_Function_Table[] = { FUNCTION_MAP };
#undef MAP

//function definitions with actual parameter types
#define MAP(x,fn_name,ret,...) ret fn_name(__VA_ARGS__);
FUNCTION_MAP
#undef MAP

//function strings ... just in case we want to print them
#define MAP(x,y,...) #y,
const char *function_strings[] = { FUNCTION_MAP };
#undef MAP

//function enum strings ... just in case we want to print them
#define MAP(x,y,...) #x,
const char *function_enum_strings[] = { FUNCTION_MAP };
#undef MAP
#undef FUNCTION_MAP

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

0 голосов
/ 12 мая 2010

X-макросы могут помочь. Например, создайте новый файл export_table_x.h, содержащий:

X_MACRO(Print),
X_MACRO(Read),
X_MACRO(Write),
X_MACRO(Process)

Тогда в вашем export_table.h используйте:

#define X_MACRO(x) ID_ ## x ## _FUNCTION
enum
{
    ID_INVALID = 0,
    #include "export_table_x.h"
};
#undef X_MACRO

А в export_table.c напишите:

#include "export_table.h"

// ...

#define X_MACRO(x) x
Function_Ptr Export_Function_Table[] =
{
    0,
    #include "export_table_x.h"
};

Одно изменение в вашей первоначальной функциональности заключается в том, что ID_PRINT_FUNCTION теперь является ID_Print_FUNCTION и т. Д.

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

...