【问题标题】:Multiple instances of a struct containing an array with different lengths which are known at compile time包含在编译时已知的具有不同长度的数组的结构的多个实例
【发布时间】:2016-11-22 14:40:51
【问题描述】:

我想要完成的是有一个可以多次使用的结构声明,仅在 C 中更改其数组成员长度。

我知道这可以通过灵活的数组成员来完成,但这会在运行时分配内存。

我想知道是否可能声明一个结构,该结构包含一个数组,该数组的大小在声明时未知,但在编译时已知 因为它的定义,使得对结构的每个实例使用的内存进行静态分配成为可能。如果不可能,那很好,我学到了一些新东西。

我的解释有点麻烦,我知道,所以我可能会做一个伪代码示例来说明我的意思。

这不是它的工作方式,而是我想做的一个例子

//file1.h
struct my_struct {
    uint8_t char_member;
    uint8_t array_member[]; //unknown length declaration
}
void function(struct my_struct, uint8_t array_size);

//file1.c
//random function for illustration purposes
void function(struct my_struct structure, uint8_t array_size){
    for(uint8_t i = 0; i < array_size; i++){
        structure.array_member[i] = 0;
    }
    return;
}

//fubar.h
extern struct my_struct foo; //still unknown length, but needed for fubar.c to aknowledge its existence.

//fubar.c

#include "file1.h"
#include "fubar.h"

//first instance of my_struct
struct my_struct foo {
    char_member = 1;    //random member definition
    array_member[64];   //definition/re-declaration of foo's array member. Here, I think, it's where it gets ugly
} 

//second instance of my_struct
struct my_struct bar {
    char_member = 23;
    array_member[128]; //definition/re-declaration of bar's array member.
} 

function(foo, 64);
function(bar, 128);

这里的重点是我可以将my_struct 作为函数的参数,而不必担心为我计划使用的每个数组大小声明不同的函数和结构类型。我可以使用void * 作为函数参数,但这会破坏类型检查,必须准确传递我想要的结构类型。我能想到的唯一方法是将带有指针的 struct 声明为成员,然后定义“链接”到 struct 实例中的指针的数组,这种方法会有点尴尬,但它会工作吗?

上下文:我在嵌入式系统中工作,我想避免动态分配。关于通过接口将结构暴露在源文件之外,虽然原则上非常好,但会消耗宝贵的堆栈空间(即我不能在其他函数之上调用许多函数)。 我正在努力的现实目标是创建一些缓冲区。作为一项练习,我试图以一种可以包装在库中然后用于不同项目的方式制作此类缓冲区。
语言是plain C,方言是c99,编译器是GCC,目标是arm cortex-m3。

【问题讨论】:

  • 请使用标准术语。 uint8_t array_member[] 是一个灵活的数组成员 (FAM)。
  • 编辑以反映正确的术语,ty。

标签: c arrays struct


【解决方案1】:

您可以使用 C 预处理器来执行此操作。例如:

//file1.h
struct my_struct {
    uint8_t char_member;
    uint8_t array_member[1];
};
#define my_struct_p(x) ((struct my_struct *)(x))

#ifndef concat
#define xconcat(a,b) a##b
#define concat(a,b) xconcat(a,b)
#endif

#define _TAG_MY_STRUCT(N) concat(my_struct,N)
#define _TYPENAME_MY_STRUCT(N) struct _TAG_MY_STRUCT(N)
#define _STRUCT_MY_STRUCT(N) \
    _TYPENAME_MY_STRUCT(N) { \
        uint8_t char_member; \
        uint8_t array_member[N]; \
    }

// Predeclared structures
_STRUCT_MY_STRUCT(32);
_STRUCT_MY_STRUCT(64);
_STRUCT_MY_STRUCT(128);

extern void function(struct my_struct *, uint8_t);

//file1.c
//random function for illustration purposes
void function(struct my_struct *structure, uint8_t array_size)
{
    for (uint8_t i = 0; i < array_size-1; i++){
        structure->array_member[i] = 0;
    }
    return;
}

//fubar.h
extern _TYPENAME_MY_STRUCT(64) foo; 
extern _TYPENAME_MY_STRUCT(128) bar; 

//fubar.c
#include "file1.h"
#include "fubar.h"

//first instance of my_struct
_TYPENAME_MY_STRUCT(64) foo;

//second instance of my_struct
_TYPENAME_MY_STRUCT(128) bar;

【讨论】:

  • 我不是宏的粉丝,但这看起来很整洁!它基本上做了我不想做的事情(声明多个结构,每个数组大小一个),但在幕后,我喜欢这样。 #define my_struct_p(x) ((struct my_struct *)(x)) 的目的是什么?将 foo 传递给函数时,是否将其转换为 my_struct 指针而不是 my_struct64 ?它是这样工作的:function(my_struct_p(&amp;foo)) ?
  • 是的,你理解的没错,只是为了转换成 struct my_struct。
【解决方案2】:

我能想到的唯一解决方案是使用 sizeof(struct my_struct) + number_of_bytes_for_array 字节数组之类的东西,并使用指向该数组的指针作为指向结构的指针。

类似

char foo_array[sizeof(struct my_struct) + 64];
struct my_struct *foo = (struct my_struct *) foo_array;

可以这样使用:

//random function for illustration purposes
void function(struct my_struct *structure, uint8_t array_size){
    for(uint8_t i = 0; i < array_size; i++){
        structure.array_member[i] = 0;
    }
    return;
}

int main(void)
{
    char foo_array[sizeof(struct my_struct) + 64];
    struct my_struct *foo = (struct my_struct *) foo_array;

    function(foo, 64);
}

【讨论】:

  • 这里没问题,因为结构只包含字节或字节数组。但是在更复杂的用例中,不会出现对齐问题吗?
  • @SergeBallesta 只要数组成员是 last 就不会有问题,因为 sizeof(struct my_struct) 包含填充。
  • 能否请教一下如何使用它?一个例子对我来说非常好理解!
  • @zakkos 添加了使用“用于说明目的的随机函数”(稍作修改的版本)的示例。注意结构参数类型的变化,以及循环条件。
【解决方案3】:

有许多解决方案仅依赖于 C 标准,然后有许多解决方案依赖于已知的 GCC 行为。

一种标准的 C 方法是将数据从结构中分离出来,并在编译时使用宏来声明它。例如:

struct buffer {
    const uint8_t  size;
    uint8_t *const data;
};

#define STATIC_BUFFER(varname, bufsize) \
    static uint8_t  buffer_data_ ## varname[bufsize]; \
    static struct buffer  varname = { .size = bufsize, \
                                      .data = buffer_data_ ## varname }

如果您想要一个名为 foo 的 16 字节静态缓冲区和一个名为 bar 的 32 字节静态缓冲区,则使用

STATIC_BUFFER(foo, 16);
STATIC_BUFFER(bar, 32);

缺点是额外的指针解引用。


使用标准 C 的另一种方法是使用预处理器宏来确定缓冲区大小。这不仅节省了指针解引用,而且缓冲区大小仅在编译时才知道,因此您将成员保存在结构中:

#define MERGE(a,b) a ## b

#define STATIC_BUFFER(varname) \
    static uint8_t  MERGE(varname, _data)[MERGE(varname, _size)]

STATIC_BUFFER(varname) 宏声明数组,其大小由先前定义的预处理器宏 varname_size 定义。例如,

#define foo_size  16
STATIC_BUFFER(foo);

#define bar_size  32
STATIC_BUFFER(bar);

您的缓冲区管理函数采用两个参数而不是一个参数:指向数据缓冲区的指针和数据缓冲区的大小:

static void do_something(uint8_t  buf[],
                         const uint8_t  size)
{
    /* ... */
}

我还会使用宏来帮助正确调用此类函数。要么

#define USING_BUFFER(varname) \
    MERGE(varname, _data), MERGE(varname, _size)

do_something(USING_BUFFER(foo));

或者,每个缓冲区管理函数一个宏,看起来也更好,

#define DO_SOMETHING_ON_BUFFER(varname) \
    do_something(MERGE(varname, _data), MERGE(varname, _size))

DO_SOMETHING_ON_BUFFER(bar);

如果您说具有头尾的循环缓冲区,大小为 2 的幂(因此您可以使用 and 运算符 &amp; 来屏蔽索引以保留在缓冲区内),我个人更喜欢后者以上:

#define MERGE(a,b) a ## b

struct buffer {
    uint8_t  head;
    uint8_t  tail;
};

/* Define the size of the buffer, a power of two,
        #define  foo_size  16
   before declaring the corresponding buffer:
        STATIC_BUFFER(foo);
   This yields two variables: foo_info and foo_data.
*/
#define STATIC_BUFFER(varname) \
   static uint8_t  MERGE(varname, _data)[MERGE(varname, _size)]; \
   static struct buffer MERGE(varname, _info)

缓冲区管理函数需要三个参数:

static void do_something(struct buffer *const info,
                         uint8_t       *const data,
                         const uint8_t        size)
{
    /* ... */
}

有趣的是(对于 C99),您可以使用 static inline 向编译器建议该函数应内联在代码中。这种方法,结合上面的方法,产生了相当好的机器代码:

static inline void buffer_add(struct buffer *const info,
                              uint8_t              data[],
                              const uint8_t        size,
                              uint8_t              item)
{
    /* Advance head; assume size is a power of two */
    info->head = (info->head + 1) & (size - 1);
    data[info->head] = item;
}

#define FOO_APPEND(c) buffer_add(&foo_info, foo_data, foo_size, c)

例如函数

void append_hello(void)
{
    FOO_APPEND('h');
    FOO_APPEND('e');
    FOO_APPEND('l');
    FOO_APPEND('l');
    FOO_APPEND('o');
}

使用 GCC 5.4.0 for arm (arm-linux-gnueabihf-gcc-5 -Wall -O2 -S -march=armv7 example.c) 基本上生成(使用 Thumb 指令集)

        .text
append_hello:
        movw    r3, #:lower16:.LANCHOR0
        movt    r3, #:upper16:.LANCHOR0
        push    {r4, r5, r6, r7}
        movs    r7, #104
        ldrb    r2, [r3]        @ zero_extendqisi2
        movs    r6, #101
        movs    r4, #108
        movs    r5, #111
        adds    r2, r2, #1
        and     r2, r2, #15
        adds    r0, r2, #1
        add     r2, r2, r3
        and     r0, r0, #15
        adds    r1, r0, #1
        strb    r7, [r2, #4]
        and     r1, r1, #15
        add     r0, r0, r3
        adds    r2, r1, #1
        add     r1, r1, r3
        and     r2, r2, #15
        strb    r6, [r0, #4]
        adds    r0, r2, #1
        strb    r4, [r1, #4]
        and     r0, r0, #15
        add     r2, r2, r3
        adds    r1, r3, r0
        strb    r0, [r3]
        strb    r4, [r2, #4]
        strb    r5, [r1, #4]
        pop     {r4, r5, r6, r7}
        bx      lr

        .bss

        .align  2
        .set    .LANCHOR0,. + 0
        .type   foo_info, %object
        .size   foo_info, 2
foo_info:
        .space  2
        .space  2

        .type   foo_data, %object
        .size   foo_data, 16
foo_data:
        .space  16

换句话说,相当漂亮和紧凑的代码。当然,你可以把它弄得更紧一些,但是 GCC 很好。

【讨论】:

  • 非常深入的回答,我会慢慢消化它!事实上,我正计划在一个结构中拥有一个带有读写头的循环缓冲区。
【解决方案4】:

我通常使用包含结构的union 和适当大小的char 数组来占用空间。 offsetof 宏可以方便地设置“空格”数组的长度。像这样的:

#include <stddef.h>
#include <stdint.h>

struct my_struct {
    uint8_t char_member;
    uint8_t array_member[];
};

void function(struct my_struct *structure, size_t array_size)
{
    for (size_t i = 0; i < array_size; i++) {
        structure->array_member[i] = 0;
    }
}

int main(void)
{
    enum { array_len = 64 };
    union {
        struct my_struct structure;
        char space[offsetof(struct my_struct, array_member[array_len])];
    } foo;

    foo.structure.char_member = 0;
    function(&foo.structure, array_len);
}

【讨论】:

    【解决方案5】:

    虽然它可能不是最好的选择,但你为什么不能像

    这样在结构中添加另一个成员
    struct my_struct {
        uint8_t char_member;
        uint8_t array_member[MAX_LENGTH]; //MAX_LENGTH shall be the maximum expected length of array_member 
        int curr_array_length;
    }
    

    因此,对于您创建的每个结构对象,您可以在此 curr_array_length 变量中跟踪 array_member 大小。

    【讨论】:

    • 我想它会起作用,但想象一下我想要一个长度为 16 且长度为 256 的字符缓冲区。这将导致 240 个非常宝贵的浪费字节。
    • 不确定您要提及的内容,结构内存分配仅在您创建结构对象后才会出现,而不是在声明本身期间。
    • 确实,所以在定义时我有(假设MAX_LENGTH 值为 256)两个长度为 256 字节的数组,其中只使用了 272 个(因为我实际上只需要 16 个字节但必须分配整个空间)。这样剩下的 240 个字节就没有被使用,也没有机会分配给其他东西,因为编译器认为我的array_member 可能需要它们。
    【解决方案6】:

    C 中没有定义的泛型类型或参数化类型。只需定义一个宏来做你想做的事并一次又一次地重用它以获得正确的定义,如

    #define MY_STRUCT(name,length) struct name {\
            uint8_t    char_member;             \
            uint8_t    array_member[length];    \
            int        curr_array_length;       \
        }
    

    因此您可以将其用作:

    MY_STRUCT(struct_64, 64) var1, var2;
    MY_STRUCT(struct_32, 32) var3, var4;
    

    在您的代码中。它将扩展到类似

    struct struct_64 {
        uint8_t    char_member;
        uint8_t    array_member[64];
        int        curr_array_length;
    } var1, var2;
    
    struct struct_32 {
        uint8_t    char_member;
        uint8_t    array_member[32];
        int        curr_array_length;
    } var3, var4;
    

    【讨论】:

      猜你喜欢
      • 2014-03-14
      • 1970-01-01
      • 2020-09-16
      • 1970-01-01
      • 2015-02-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多