代码之家  ›  专栏  ›  技术社区  ›  101010

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

  •  0
  • 101010  · 技术社区  · 6 年前

    这个问题是一个技巧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,但随后需要从所有on-do-tuff函数中删除静态关键字,从而使命名空间混乱。

    在Linux内核中,它们以某种方式在单独的文件中定义内核模块,然后通过Linker Magic,它们能够被加载到单片电路中。

    1 回复  |  直到 6 年前
        1
  •  2
  •   Nominal Animal    6 年前

    使用专用的ELF部分“收集”数据结构。

    例如,在中定义数据结构 信息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'th)是正确的,其余的结构都会混乱。实际上,C编译器和链接器在“array”部分中的每个结构的大小上存在分歧。

    请注意,可以添加预处理器宏来微调 信息对齐 对于您使用的每个体系结构,您也可以在编译时覆盖它,例如在makefile中。(对于GCC,供应 -DINFO_ALIGNMENT=32 例如。)

    这个 used 属性确保在对象文件中发出定义,即使在同一数据文件中没有以其他方式引用该定义。这个 section("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 ,以获取 信息 第节。在你的 主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 要定义操作,请使用中定义的设施 哦! :

    #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 */
    

    基本操作集在 操作基本.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");
    

    为了简单起见,计算器希望每个值和操作数都是单独的命令行参数。我们的 主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;
    }
    

    请注意,如果有许多操作,那么构造一个哈希表来加快操作查找将非常有意义。

    最后,我们需要 生成文件 要将所有这些联系在一起:

    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 $@
    

    因为这个论坛不保存 标签 而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 .

    现在,让我们添加一些新的操作, 操作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- 在最后一个二进制文件中,您只需要重新编译源文件: 全部清除 . 正在运行 /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 ,如预期。