环形队列

在处理网络分包,包未收完整时,必定需要一个缓冲区来缓存数据,ring_buffer是最常用的选择。它是一个比较简单的数据结构,在学《数据结构》时想必大家都实现过,但我前几天就被它教育了一把,write时,一处分支漏了个等号,些许情况下会缓冲区溢出,导致堆栈一些数据被破坏,让我足足花了一整天才找到这个bug,我错误的地方如下:

 1 int
 2 rbuf_write(struct ring_buffer *self,const char *data,int n){
 3     assert(self);
 4     assert(data);
 5     if(n <= 0){
 6         return 0;
 7     }
 8     for(;;){
 9         unsigned int len = rbuf_length(self) + n;
10         if(len >= INT_MAX){
11             return -1;
12         }
13         if(self->cap - rbuf_length(self) -1 < n){
14             rbuf__expand(self);
15         }
16         else{
17                         //少了等号
18             if(self->tail >self->head){
19                 int copy1 = self->cap - self->tail;
20                 if(copy1 > n ){
21                     copy1 = n;
22                 }
23                 memcpy(self->buf + self->tail,data,copy1);
24                 if(copy1 < n){
25                     memcpy(self->buf,data + copy1,n - copy1);
26                 }
27             }
28             else{
29                 memcpy(self->buf + self->tail,data,n);
30             }
31             self->tail = (self->tail + n ) % self->cap;
32             return n;
33         }
34     }
35 }    

第18行的判断应该是>=,否则当n>(self->cap - self->tail)就会溢出。


这里再来回忆一番。我要的接口如下:

1 struct ring_buffer;
2 struct ring_buffer * rbuf_create(int n);
3 void rbuf_destory(struct ring_buffer *);
4 int rbuf_length(struct ring_buffer *);
5 //return 0:length shorter than n, n:success
6 int rbuf_read(struct ring_buffer *self,char *dest,int n);
7 //return -1:length over INT_MAX  n:success
8 int rbuf_write(struct ring_buffer *self,const char *data,int n);

读写时要么读写指定的大小,要么失败。实现它,只需注意如下几点,就不会写错了:

  • 头进尾出,头指向下一个出队的位置,尾指向下一个入队的位置。
  • head == tail时为空队列
  • 队列长度为:(tail - head + cap) % cap
  • 因为头尾相等为空,所以队列可用空间要比容量少1,因次队列满为:队列长度 = cap - 1
  • 头尾的下一位置为:($@+n) % cap
  • 要根据头尾的位置做分段处理

具体实现就放在github上了

 

posted @ 2016-08-15 23:51  watercold  阅读(627)  评论(0编辑  收藏  举报