如何在编译时定义一个由不同模块的静态(私有)结构组成的结构数组?

时间:2018-05-27 18:24:09

标签: c arrays gcc clang

这个问题是一个技巧C问题或一个技巧铿锵/ gcc问题。我不确定哪个。

我的意思就像我做的那样,因为最后一个数组是在main.c中,但是数组中的结构是在C模块中定义的。

我想要做的最终目标是能够在单独的C模块中定义结构,然后在程序启动时使这些结构在连续数组中可用。我不想使用任何动态代码来声明数组并放入元素。

我希望这一切都在编译或链接时完成 - 而不是在运行时。

我希望最终得到一个整体的内存blob,可以从程序启动开始设置。

为了Stack Overflow的问题,我认为如果我把它们想象成“驱动程序”(就像在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,但是我需要从所有on_do_stuff函数中删除static关键字,从而使命名空间变得混乱。

在Linux内核中,他们以某种方式将内核模块定义在单独的文件中,然后通过链接器魔术,将它们加载到单片中

1 个答案:

答案 0 :(得分:2)

使用专用的ELF部分来收集&#34;数据结构。

例如,将 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&#39;)将是正确的,其余结构将出现乱码。实际上,C编译器和链接器在大小上不一致部分中的每个结构&#34;数组&#34;。)

请注意,您可以添加预处理器宏来微调您使用的每个体系结构的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,只需确保在main.c中的其他地方不使用SOMENAME,所以可以使用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 ,并且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将所有以ops-开头的C源文件编译成最终的二进制文件,所以你唯一需要做的就是重新编译源代码: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