关于循环缓冲区C实现的问题

时间:2011-08-24 16:00:23

标签: c data-structures

我已经看到了循环缓冲区的以下实现:

http://en.wikipedia.org/wiki/Circular_buffer

/**< Buffer Size */
#define BUFFER_SIZE    10
#define NUM_OF_ELEMS   (BUFFER_SIZE-1)

/**< Circular Buffer Types */
typedef unsigned char INT8U;
typedef INT8U KeyType;
typedef struct
{
    INT8U writePointer; /**< write pointer */
    INT8U readPointer;  /**< read pointer */
    INT8U size;         /**< size of circular buffer */
    KeyType keys[0];    /**< Element of circular buffer */
} CircularBuffer;

/**< Init Circular Buffer */
CircularBuffer* CircularBufferInit(CircularBuffer** pQue, int size)
{
    int sz = size*sizeof(KeyType)+sizeof(CircularBuffer);
    *pQue = (CircularBuffer*) malloc(sz);
    if(*pQue)
    {
        printf("Init CircularBuffer: keys[%d] (%d)\n", size, sz);
        (*pQue)->size=size;
        (*pQue)->writePointer = 0;
        (*pQue)->readPointer  = 0;
    }
    return *pQue;
}

int main(int argc, char *argv[])
{
    CircularBuffer* que;
    KeyType a = 101;
    int isEmpty, i;

    CircularBufferInit(&que, BUFFER_SIZE);
    ...
}

以下是问题:

Q1&GT;为什么代码使用以下行来定义变量

KeyType keys[0];    /**< Element of circular buffer */

Q2&GT;为什么代码计算分配缓冲区的大小,如下所示:

int sz = size*sizeof(KeyType)+sizeof(CircularBuffer);

Q3&GT;为什么pQue指向一个大于CircularBuffer大小的缓冲区,但仍然可以 直接指它的成员?

(*pQue)->size=size;
(*pQue)->writePointer = 0;
(*pQue)->readPointer  = 0;

4 个答案:

答案 0 :(得分:2)

第一项只是指定struct中的元素使用数组语法,但实际上并未声明为任何大小的数组。

malloc分配循环缓冲区大小(占所有非关键字段)和关键字段(size * sizeof(KeyType))。请注意,键[0]实际上的大小为零,因此没有键字段被计算两次。

缓冲区实际上并不大于循环缓冲区的大小,分配(如上所述)是一次性分配,以便在一次传递中容纳循环缓冲区和控制元素(size,readPointer,writePointer)

这样做的全部原因是因为C没有检查你是否走出了数组的末尾。在一种强制数组边界的语言中,第一次尝试使用它时,你会得到类似于Java的ArrayOutOfBoundsException,因为要使用keys [0],你必须声明一个大小(至少)一个的键数组,像键[1]。

换句话说,在没有编码固定大小的情况下,优化缓冲区分配一次是一些C特定的黑客攻击。它起作用的原因是因为数组偏移严格实现为(基地址+索引* sizeof(数组类型))。

答案 1 :(得分:1)

int sz = size*sizeof(KeyType)+sizeof(CircularBuffer);

size =循环缓冲区中元素的数量

sizeof(KeyType)=单个KeyType元素的大小

(size * sizeof)=用于存储所有KeyType元素的总空间量

sizeof(CircularBuffer)=因为循环缓冲区存储的附加字段不是循环缓冲区元素

  

为什么代码使用以下行来定义变量键?

     

KeyType键[0]; / ** LT;循环缓冲区元素* /

我无法理解你的怀疑。这很常见。循环缓冲区用元素数组表示,其类型为KeyType。 如果你想知道为什么它使用KeyType而不是直接unsigned int那是因为:

  1. 这种方式更清晰。新类型明确地介绍了数组的用途。
  2. 重构目的:如果我们决定更改KeyType的类型,我们只需修改typedef。

答案 2 :(得分:1)

正在发生的事情是,在内存块的开始处有一个“头”结构,后面是可变数量的“细节”结构。这曾经是一种常见的做事方式,早在旧日;你可以通过调用malloc()来创建所有结构,这样对性能有好处。您可能也会经常使用文件处理代码或网络协议来查看此模式,您将获得可变大小的数据块,其精确大小在标头中给出。 (Excel BIFF格式和TCP

1:零长度数组曾经是在结构末尾指定可变长度数组的常用方法。

2:它为头部分配sizeof(CircularBuffer)字节,并为size * sizeof(KeyType)分配,以便可以有那么多KeyType。最终得到一个内存块,它同时包含标题和键。

3:没有指向你分配的内存块中的区域。如果它指向分配区域的OUTSIDE,那就不好了,但事实并非如此。

答案 3 :(得分:1)

此代码实现了一些有时称为“弹性数组”的东西。目标是在编译时不知道数组的大小时创建包含数组的结构。因此,结构定义为零大小的数组作为最后一个元素。分配时,将所需大小添加到malloc调用中。因为C保证malloc'd块是连续的,并且因为它不进行数组边界检查,所以可以将过去的0索引到附加内存中,并将其视为常规数组元素。

请注意,此技术仅在malloc结构时有效。如果它被声明为局部变量,则阵列将没有额外的空间。