环形队列
在处理网络分包,包未收完整时,必定需要一个缓冲区来缓存数据,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上了