Как определить массив структур во время компиляции, состоящий из статических (частных) структур из отдельных модулей? - PullRequest
0 голосов
/ 27 мая 2018

Этот вопрос является вопросом трюка C или трюка clang / gcc.Я не уверен, какой.

Я сформулировал это так, как я сделал, потому что последний массив находится в main.c, но структуры, которые находятся в массиве, определены в модулях C.

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

Я бы хотел, чтобы все это делалось во время компиляции или во время компоновки, а не во время выполнения.

Я хочу закончить с монолитным блобом памяти, который настраивается сразу после запуска программы.

Ради вопроса переполнения стека, я подумал, что это будет иметь смысл, если япредставлял их как «драйверы» (как в ядре Linux). Идем с этим ...

Каждый модуль является драйвером.Поскольку команда сложная, я не знаю, сколько в итоге будет гонщиков.

Требования:

  • Загружается в непрерывную память (массив)
  • Загружается в память при запуске программы
  • , установленной компилятором / компоновщиком,не динамический код
  • драйвер существует, потому что для него существует исходный код (динамический код для их загрузки отсутствует)
  • Избегайте загромождения кода

Вотнадуманный пример:

// myapp.h
//////////////////////////

struct state
{
    int16_t data[10];
};

struct driver
{
    char name[255];
    int16_t (*on_do_stuff) (struct state *state);
    /* other stuff snipped out */
};

// drivera.c
//////////////////////////

#include "myapp.h"

static int16_t _on_do_stuff(struct state *state)
{
    /* do stuff */
}

static const struct driver _driver = {
    .name = "drivera",
    .on_do_stuff = _on_do_stuff
};

// driverb.c
//////////////////////////

#include "myapp.h"

static int16_t _on_do_stuff(struct state *state)
{
    /* do stuff */
}

static const struct driver _driver = {
    .name = "driverb",
    .on_do_stuff = _on_do_stuff
};

// driverc.c
//////////////////////////

#include "myapp.h"

static int16_t _on_do_stuff(struct state *state)
{
    /* do stuff */
}

static const struct driver _driver = {
    .name = "driverc",
    .on_do_stuff = _on_do_stuff
};

// main.c
//////////////////////////

#include <stdio.h>

static struct driver the_drivers[] = {
    {drivera somehow},
    {driverb somehow},
    {driverc somehow},
    {0}
};

int main(void)
{
    struct state state;
    struct driver *current = the_drivers;

    while (current != 0)
    {
        printf("we are up to %s\n", current->name);
        current->on_do_stuff(&state);
        current += sizeof(struct driver);
    }

    return 0;
}

Это не работает точно.

Идеи:

  • На структурах уровня модуля я мог бы удалить статические ключевые слова const, но я не уверен, как получить их в массив во время компиляции
  • Я мог бы переместить все структуры уровня модуля в main.c, но тогда мне нужно было бы удалить ключевое слово static из всех функций on_do_stuff и таким образом загромождать пространство имен.

В ядре Linux они каким-то образом определяют модули ядра в отдельных файлах, а затем, используя магию компоновщика, могут загружаться в монолитную среду

1 Ответ

0 голосов
/ 28 мая 2018

Используйте выделенный раздел ELF для "сбора" структур данных.

Например, определите свою структуру данных в info.h как

#ifndef   INFO_H
#define   INFO_H

#ifndef  INFO_ALIGNMENT
#if defined(__LP64__)
#define  INFO_ALIGNMENT  16
#else
#define  INFO_ALIGNMENT  8
#endif
#endif

struct info {
    long  key;
    long  val;
} __attribute__((__aligned__(INFO_ALIGNMENT)));

#define  INFO_NAME(counter)  INFO_CAT(info_, counter)
#define  INFO_CAT(a, b)      INFO_DUMMY() a ## b
#define  INFO_DUMMY()

#define  DEFINE_INFO(data...) \
         static struct info  INFO_NAME(__COUNTER__) \
             __attribute__((__used__, __section__("info"))) \
             = { data }

#endif /* INFO_H */

.Макрос INFO_ALIGNMENT - это выравнивание, используемое компоновщиком для размещения каждого символа отдельно в секции info.Важно, чтобы компилятор C согласился, поскольку в противном случае содержимое раздела не может рассматриваться как массив.(Вы получите неправильное количество структур, и только первая (плюс каждый N-й) будет правильной, остальные структуры искажены. По сути, компилятор C и компоновщик не согласились с размером каждой структуры враздел «массив».)

Обратите внимание, что вы можете добавить макросы препроцессора для точной настройки INFO_ALIGNMENT для каждой используемой вами архитектуры, но вы также можете переопределить ее, например, в вашем Makefile при компиляциивремя.(Для GCC укажите -DINFO_ALIGNMENT=32, например.)

Атрибут used гарантирует, что определение передается в объектном файле, даже если на него не ссылаются иначе в том же файле данных.Атрибут section("info") помещает данные в специальный раздел info в объектном файле.Имя раздела (info) зависит от вас.

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

В исходных файлах вы определяете объекты данных как, например,

#include "info.h"

/* Suggested, easy way */
DEFINE_INFO(.key = 5, .val = 42);

/* Alternative way, without relying on any macros */
static struct info  foo  __attribute__((__used__, __section__("info"))) = {
    .key = 2,
    .val = 1
};

Компоновщик предоставляет символы __start_info и __stop_info, чтобы получить структуры в разделе info.В вашем main.c используйте, например,

#include "info.h"

extern struct info  __start_info[];
extern struct info  __stop_info[];

#define  NUM_INFO  ((size_t)(__stop_info - __start_info))
#define  INFO(i)   ((__start_info) + (i))

, чтобы вы могли перечислить все информационные структуры.Например,

int main(void)
{
    size_t  i;

    printf("There are %zu info structures:\n", NUM_INFO);
    for (i = 0; i < NUM_INFO; i++)
        printf("  %zu. key=%ld, val=%ld\n", i,
            __start_info[i].key, INFO(i)->val);

    return EXIT_SUCCESS;
}

Для иллюстрации я использовал оба доступа к массиву __start_info[] (вы можете, конечно, #define SOMENAME __start_info, если хотите, просто убедитесь, что вы не используете SOMENAME где-либо еще в main.c, так что вместо этого вы можете использовать SOMENAME[] в качестве массива), а также макрос INFO().


Давайте рассмотрим практический пример калькулятора RPN.

Мыиспользуйте секцию ops для определения операций, используя средства, определенные в ops.h :

#ifndef   OPS_H
#define   OPS_H
#include <stdlib.h>
#include <errno.h>

#ifndef  ALIGN_SECTION
#if defined(__LP64__) || defined(_LP64)
#define  ALIGN_SECTION  __attribute__((__aligned__(16)))
#elif defined(__ILP32__) || defined(_ILP32)
#define  ALIGN_SECTION  __attribute__((__aligned__(8)))
#else
#define  ALIGN_SECTION
#endif
#endif

typedef struct {
    size_t   maxsize;   /* Number of values allocated for */
    size_t   size;      /* Number of values in stack */
    double  *value;     /* Values, oldest first */
} stack;
#define  STACK_INITIALIZER  { 0, 0, NULL }

struct op {
    const char  *name;            /* Operation name */
    const char  *desc;            /* Description */
    int        (*func)(stack *);  /* Implementation */
} ALIGN_SECTION;

#define  OPS_NAME(counter)  OPS_CAT(op_, counter, _struct)
#define  OPS_CAT(a, b, c)   OPS_DUMMY()  a ## b ## c
#define  OPS_DUMMY()

#define  DEFINE_OP(name, func, desc) \
         static struct op  OPS_NAME(__COUNTER__) \
         __attribute__((__used__, __section__("ops"))) = { name, desc, func }

static inline int  stack_has(stack *st, const size_t num)
{
    if (!st)
        return EINVAL;

    if (st->size < num)
        return ENOENT;

    return 0;
}

static inline int  stack_pop(stack *st, double *to)
{
    if (!st)
        return EINVAL;

    if (st->size < 1)
        return ENOENT;

    st->size--;

    if (to)
        *to = st->value[st->size];

    return 0;
}


static inline int  stack_push(stack *st, double val)
{
    if (!st)
        return EINVAL;

    if (st->size >= st->maxsize) {
        const size_t  maxsize = (st->size | 127) + 129;
        double       *value;

        value = realloc(st->value, maxsize * sizeof (double));
        if (!value)
            return ENOMEM;

        st->maxsize = maxsize;
        st->value   = value;
    }

    st->value[st->size++] = val;

    return 0;
}

#endif /* OPS_H */

Основной набор операций определен в ops-basic.c :

#include "ops.h"

static int do_neg(stack *st)
{
    double  temp;
    int     retval;

    retval = stack_pop(st, &temp);
    if (retval)
        return retval;

    return stack_push(st, -temp);
}

static int do_add(stack *st)
{
    int  retval;

    retval = stack_has(st, 2);
    if (retval)
        return retval;

    st->value[st->size - 2] = st->value[st->size - 1] + st->value[st->size - 2];
    st->size--;

    return 0;
}

static int do_sub(stack *st)
{
    int  retval;

    retval = stack_has(st, 2);
    if (retval)
        return retval;

    st->value[st->size - 2] = st->value[st->size - 1] - st->value[st->size - 2];
    st->size--;

    return 0;
}

static int do_mul(stack *st)
{
    int  retval;

    retval = stack_has(st, 2);
    if (retval)
        return retval;

    st->value[st->size - 2] = st->value[st->size - 1] * st->value[st->size - 2];
    st->size--;

    return 0;
}

static int do_div(stack *st)
{
    int  retval;

    retval = stack_has(st, 2);
    if (retval)
        return retval;

    st->value[st->size - 2] = st->value[st->size - 1] / st->value[st->size - 2];
    st->size--;

    return 0;
}

DEFINE_OP("neg", do_neg, "Negate current operand");
DEFINE_OP("add", do_add, "Add current and previous operands");
DEFINE_OP("sub", do_sub, "Subtract previous operand from current one");
DEFINE_OP("mul", do_mul, "Multiply previous and current operands");
DEFINE_OP("div", do_div, "Divide current operand by the previous operand");

Калькулятор ожидает, что каждое значение и операнд будут отдельным аргументом командной строки для простоты.Наш main.c содержит поиск операций, базовое использование, анализ значений и печать результата (или ошибки):

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include "ops.h"

extern struct op __start_ops[];
extern struct op  __stop_ops[];

#define  NUM_OPS  ((size_t)(__stop_ops - __start_ops))


static int  do_op(stack *st, const char *opname)
{
    struct op  *curr_op;

    if (!st || !opname)
        return EINVAL;

    for (curr_op = __start_ops; curr_op < __stop_ops; curr_op++)
        if (!strcmp(opname, curr_op->name))
            break;

    if (curr_op >= __stop_ops)
        return ENOTSUP;

    return curr_op->func(st);
}


static int  usage(const char *argv0)
{
    struct op  *curr_op;

    fprintf(stderr, "\n");
    fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv0);
    fprintf(stderr, "       %s RPN-EXPRESSION\n", argv0);
    fprintf(stderr, "\n");
    fprintf(stderr, "Where RPN-EXPRESSION is an expression using reverse\n");
    fprintf(stderr, "Polish notation, and each argument is a separate value\n");
    fprintf(stderr, "or operator. The following operators are supported:\n");

    for (curr_op = __start_ops; curr_op < __stop_ops; curr_op++)
        fprintf(stderr, "\t%-14s  %s\n", curr_op->name, curr_op->desc);

    fprintf(stderr, "\n");

    return EXIT_SUCCESS;
}


int main(int argc, char *argv[])
{
    stack  all = STACK_INITIALIZER;
    double val;
    size_t i;
    int    arg, err;
    char   dummy;

    if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
        return usage(argv[0]);

    for (arg = 1; arg < argc; arg++)
        if (sscanf(argv[arg], " %lf %c", &val, &dummy) == 1) {
            err = stack_push(&all, val);
            if (err) {
                fprintf(stderr, "Cannot push %s to stack: %s.\n", argv[arg], strerror(err));
                return EXIT_FAILURE;
            }
        } else {
            err = do_op(&all, argv[arg]);
            if (err == ENOTSUP) {
                fprintf(stderr, "%s: Operation not supported.\n", argv[arg]);
                return EXIT_FAILURE;
            } else
            if (err) {
                fprintf(stderr, "%s: Cannot perform operation: %s.\n", argv[arg], strerror(err));
                return EXIT_FAILURE;
            }
        }

    if (all.size < 1) {
        fprintf(stderr, "No result.\n");
        return EXIT_FAILURE;
    } else
    if (all.size > 1) {
        fprintf(stderr, "Multiple results:\n");
        for (i = 0; i < all.size; i++)
            fprintf(stderr, "  %.9f\n", all.value[i]);
        return EXIT_FAILURE;
    }

    printf("%.9f\n", all.value[0]);
    return EXIT_SUCCESS;
}

Обратите внимание, что, если было много операций, построение хэш-таблицычтобы ускорить поиск операций, было бы много смысла.

Наконец, нам нужен Makefile , чтобы связать все это вместе:

CC      := gcc
CFLAGS  := -Wall -O2 -std=c99
LDFLAGS := -lm
OPS     := $(wildcard ops-*.c)
OPSOBJS := $(OPS:%.c=%.o)
PROGS   := rpncalc

.PHONY: all clean

all: clean $(PROGS)

clean:
        rm -f *.o $(PROGS)

%.o: %.c
        $(CC) $(CFLAGS) -c $^

rpncalc: main.o $(OPSOBJS)
        $(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@

Поскольку этот форум делаетне сохраняйте Tab s, и make требует их для отступа, вам, вероятно, нужно исправить отступ после вставки копии выше.Я использую sed -e 's|^ *|\t|' -i Makefile

Если вы скомпилируете (make clean all) и запустите (./rpncalc) выше, вы увидите информацию об использовании:

Usage: ./rpncalc [ -h | --help ]
       ./rpncalc RPN-EXPRESSION

Where RPN-EXPRESSION is an expression using reverse
Polish notation, and each argument is a separate value
or operator. The following operators are supported:
        div             Divide current operand by the previous operand
        mul             Multiply previous and current operands
        sub             Subtract previous operand from current one
        add             Add current and previous operands
        neg             Negate current operand

и если вы запуститенапример, ./rpncalc 3.0 4.0 5.0 sub mul neg, вы получите результат 3.000000000.

Теперь давайте добавим несколько новых операций, ops-sqrt.c :

#include <math.h>
#include "ops.h"

static int do_sqrt(stack *st)
{
    double  temp;
    int     retval;

    retval = stack_pop(st, &temp);
    if (retval)
        return retval;

    return stack_push(st, sqrt(temp));
}

DEFINE_OP("sqrt", do_sqrt, "Take the square root of the current operand");

ПосколькуMakefile выше компилирует все исходные файлы C, начиная с ops-, в конечный двоичный файл, единственное, что вам нужно сделать, это перекомпилировать исходный код: make clean all.При выполнении ./rpncalc теперь выводится

Usage: ./rpncalc [ -h | --help ]
       ./rpncalc RPN-EXPRESSION

Where RPN-EXPRESSION is an expression using reverse
Polish notation, and each argument is a separate value
or operator. The following operators are supported:
        sqrt            Take the square root of the current operand
        div             Divide current operand by the previous operand
        mul             Multiply previous and current operands
        sub             Subtract previous operand from current one
        add             Add current and previous operands
        neg             Negate current operand

, и у вас есть новый оператор sqrt.

Тестирование, например, ./rpncalc 1 1 1 1 add add add sqrt, дает 2.000000000, как и ожидалось.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...