队列的静态初始化

static initialization of queue

提问人:deppep 提问时间:2/8/2023 最后编辑:deppep 更新时间:2/8/2023 访问量:285

问:

我正在研究嵌入式系统算法的高依赖性实现。

在:main.c

    //.. in main()
    int queue_buffer[QUEUE_LEN + 1] = { 0 };
    Queue queue;
    queue_init(&queue, QUEUE_LEN, queue_buffer);
    do_things_on_queue(&queue);
    //.. in main()

在:queue.c

void queue_init(Queue *q, int len, int *data) {
    q->head = 0;
    q->tail = 0;
    q->len = len;
    q->data = data; // an array of length `len + 1`
}

在:queue.h

typedef struct queue {
    int head;
    int tail;
    int len;
    int *data;
} Queue;

我想要 1.不得不不知道;和 2.使用 malloc 进行初始化,而是静态地进行。main.cQueuequeue_buffer_

这意味着理想情况下是:main.c

    //.. in some function
    Queue *queue = queue_init(something_eventually);
    do_things_with_queue(queue);
    //.. in some function

是否可以在 C99 中进行修改以实现此目的?如果是这样,最好的方法是什么?queue_initqueue.c


暂定解决方案

我知道这篇文章中讨论的技术,但如果不使用 .我确信我最多会同时有 4 个队列。这让我想到我可以将队列的内存池声明为大小为 4 的队列的静态全局数组。在这种情况下可以使用全局变量吗?malloc

@KamilKuk建议只需要返回结构本身,因为在编译时是已知的。这需要满足以下条件:queue_initQUEUE_LEN

在:queue.c

Queue queue_init(void) {
    Queue q;
    
    q.head = 0;
    q.tail = 0;
    q.len = QUEUE_LEN;
    for (int i=0; i < QUEUE_LEN; i++)
        q.data[i] = 0;
    return q;
}

在:queue.h

typedef struct queue {
    int head;
    int tail;
    int len;
    int data[QUEUE_LEN];
} Queue;

Queue queue_init(void);

这似乎大大简化了结构初始化。 但是,这并不能解决隐私问题,因为应该知道要初始化此结构。main.cQueue


谢谢。

C 嵌入式 静态初始化

评论

1赞 Eugene Sh. 2/8/2023
如果只有一个 的实例,请将其隐藏在其中,并仅提供在该特定实例上工作的 API。Queuequeue.c
1赞 Eugene Sh. 2/8/2023
然后隐藏所有四个。只要你有一些固定的号码,你就可以隐藏它们。不过,您需要以某种方式引用它们(通过序号或其他东西)
2赞 Eugene Sh. 2/8/2023
由于您要静态初始化队列,这意味着您有一些固定容量。因此,只需为每个队列分配静态缓冲区即可。
1赞 KamilCuk 2/8/2023
我不明白,你想静态初始化队列,或者有“在某些功能中”的代码。只需从队列初始化返回队列即可。 您提供的代码不会这样做。 您在下面显示的代码是“在某些函数中”,它不能是静态的。Queue queue = queue_initnot use malloc for intializing queue_buffer_but rather do it statically
1赞 KamilCuk 2/8/2023
我不知道我在建议什么。如果所有队列都是恒定的,只需使缓冲区成为结构的一部分。我相信你可以考虑研究 C 语言中的一些主题,比如对象的生命周期取决于存储持续时间或复合文字。

答:

1赞 KamilCuk 2/8/2023 #1

我通常会做:

// queue.h
#define QUEUE_INIT(data, len)  { .len = len, .data = data }
#define QUEUE_INIT_ON_STACK(len)  QUEUE_INIT((char[len]){0}, len)

// main.c
static Queue queue = QUEUE_INIT_ON_STACK(QUEUE_LEN  + 1);

至于 PIMPL 习语,它很容易用描述符实现,就像 LINUX 中的文件描述符一样,尤其是当计数是静态的时。

// queue.h
typedef Queue int;
void do_things_with_queue(Queue);

// queue.c
struct RealQueue { stuff; };
static struct RealQeueue arr[4] = { stuff };
static struct RealQeueue *get_RealQueue(Queue i) {
     assert(0 <= i && i < sizeof(arr)/sizeof(*arr));
     return &arr[i];
}
void do_things_with_queue(Queue i) {
    struct RealQueue *queue = get_RealQueue(i);
}

// main.c
static Queue queue = 1;
// etc.

或者,您可以打破所有地狱并同步源文件和头文件之间的对齐方式:

// queue.h
struct Queue {
    // This has to be adjusted __for each compiler and environment__
    alignas(60) char data[123];
};
 
#define QUEUE_INIT() { 0xAA, 0xBB, etc.. constant precomputed data }

// queue.c
struct RealQeueue { stuff; };
static_assert(alingof(struct RealQueue) == alignof(struct Queue), "");
static_assert(sizeof(struct RealQueue) == sizeof(struct Queue), "");
void do_things_with_queue(Queue *i) {
    struct RealQueue *queue = (struct RealQueue*)i->data;
}
1赞 Tom V 2/8/2023 #2

执行此操作的最佳方法是将缓冲区及其大小传递给 init 函数,就像您已经拥有的那样。

担心调用函数而不是在编译时修复数据是一个非常糟糕的主意。像这样的微小初始化的执行时间和代码大小都可以忽略不计。仅仅为了在启动时保存一些指令而使代码界面变得笨拙,这不仅是浪费精力,还会使代码难以维护,并有引入错误的风险。

有许多嵌入式系统或库提供了一个宏,该宏一次性声明存储阵列和控制结构,并为其指定一个只有库知道的名称,然后每次访问项目时都必须使用宏生成名称。例如,您可以查看 CMSIS-OS 中的 osMailQDef。不过,我真的不推荐这种方法。它太容易出错了,而以通常的方式做很容易阅读,任何审稿人都能立即发现错误。

评论

0赞 deppep 2/8/2023
感谢您的有趣链接和建议。这当然是令人欣慰:P!然而,在我的“真实”案例中,我必须处理多个结构,每个结构都包含不同的缓冲区。结果是初始化代码占用了半页,而我只希望只向代码的用户提供一些函数,使他无法访问结构的成员。这不可能吗?KamilCuk 在 op 注释中建议返回结构本身以避免冗长的初始化。这似乎有效,但并没有为结构的隐私提供解决方案。非常感谢!
1赞 Tom V 2/8/2023
如果你在结构中需要许多不同的成员,那么只需传递一个单字节数组及其大小,并在 init 函数中将其划分。您甚至可以从字节数组中为结构体本身留出空间,然后返回结构指针。只是不要忘记对齐。
1赞 John Bollinger 2/8/2023 #3

我想要 1.不得不不知道;和 2.不用于初始化,而是静态地进行。main.cQueuemallocqueue_buffer_

这意味着理想情况下,main.c 将是:

    //.. in some function
    Queue queue = queue_init(something_eventually);
   do_things_with_queue(&queue);
   //.. in some function

不,您的目标并不意味着所描述的解决方案。不能在类型定义不可见的任何位置声明或使用该类型的对象。这直接来自语言的规则,但如果你想要一个更有意义的理由,那么考虑到即使没有访问 的任何成员,它仍然需要定义来知道要为一个保留多少空间。QueuemainQueue

对我来说,使类型不透明(或任何地方)是否有用并不明显,但如果这是您想要的,那么在该范围内,您可以转发声明它,从不定义它,并且只使用指向它的指针:Queuemain.c

typedef struct queue Queue;

// ...

    Queue *queue = queue_init(something_eventually);
    do_things_with_queue(queue);

要使它在没有动态内存分配的情况下工作,指向的对象必须具有静态存储持续时间,但这并不意味着它们必须是全局的 - 无论是在通过具有外部链接的名称访问的意义上,还是在文件范围内声明的意义上。Queue

此外,您还可以选择自动分配数组,如示例代码中所示,以便在不使用内存时不会将内存占用在队列中。如果您愿意,可以将其包含在一两个宏中,以增加易用性(作为练习)。data

例如,
queue.h

typedef struct queue Queue;

Queue *queue_init(int queue_size, int queue_data[]);
void queue_release(Queue *queue);

queue.c

#include "queue.h"

struct queue {
    int head;
    int tail;
    int len;
    int *data;
};

Queue *queue_init(int queue_len, int queue_data[]) {
    // queue_pool has static storage duration and no linkage
    static Queue queue_pool[4] = {{0}};

    // Find an available Queue, judging by the data pointers

    for (Queue *queue = queue_pool;
            queue < queue_pool + sizeof(queue_pool) / sizeof(*queue_pool);
            queue++) {
        if (queue->data == NULL) {
            // This one will do.  Initialize it and return a pointer to it.
            queue->head = 0;
            queue->tail = 0;
            queue->len = queue_len;
            queue->data = queue_data;

            return queue;
        }
    }

    // no available Queue
    return NULL;
}

void queue_release(Queue *queue) {
    if (queue) {
        queue->data = NULL;
    }
}

main.c

    // ... in some function

    int queue_data[SOME_QUEUE_LENGTH];
    Queue *queue = queue_init(SOME_QUEUE_LENGTH, queue_data);
    do_things_with_queue(queue);
    queue_release(queue);

    // ...

当然,如果您愿意,可以将队列数据直接放入队列结构中,就像在暂定解决方案中一样,并且可以在那里提供一个标志来指示队列当前是否正在使用中。这将减轻用户提供存储的任何需求,但代价是在整个程序期间占用所有队列的所有元素的存储。

评论

0赞 deppep 2/8/2023
你说得对,“理想”应该知道而不是自己,我会纠正操作。我有一个问题:使用静态局部变量代替具有文件范围的变量有什么好处?非常感谢您的出色解决方案。main.cQueue *Queue
0赞 John Bollinger 2/8/2023
@deppep,将池放在局部变量中不会给它带来任何联系:即使是同一翻译单元中的其他函数也无法直接访问这些对象,即使它们知道有关该类型的所有信息。这可能是可取的,也可能是不可取的。但是,要使其正常工作,必须声明变量以为其提供静态存储持续时间。生成的对象的生存期不受函数执行的限制;相反,它是程序运行的整个持续时间。QueueQueuestatic
0赞 John Bollinger 2/8/2023
(注意:文件范围声明的含义与函数内部声明的含义不同。static