RingBuffer 环形缓冲区----镜像指示位

news2024/9/24 13:24:19

 文字和图片参考和来自这些文章:

大疆嵌入式软件编程题==找鞍点_已知循环缓冲区是一个可以无限循环读写的缓冲区,当缓冲区满了还继续写的话就会覆_一禅的师兄的博客-CSDN博客

ring buffer,一篇文章讲透它? - 知乎 (zhihu.com)

1 概述

1.1 什么是ringbuff

ring buffer称作环形缓冲区,也称作环形队列(circular queue),是一种用于表示一个固定尺寸、头尾相连的缓冲区的数据结构,适合缓存数据流。

1.2 宏定义

这个宏定义用于计算向下对齐的地址。它接收两个参数:sizealignsize 是要对齐的内存大小,align 是所需的对齐大小。这个宏返回一个值,该值是 size 向下对齐到 align 边界后的地址。

#define DATA_ALIGN_DOWN(size, align)      ((size) & ~((align) - 1))

例如,假设我们有一个 32 位的系统,并且我们需要将数据对齐到 64 位的边界。我们可以使用 DATA_ALIGN_DOWN 宏来计算数据对齐后的地址:

#define DATA_ALIGN_DOWN(size, align)      ((size) & ~((align) - 1))

int data[10];
int *aligned_data = DATA_ALIGN_DOWN(data, 8);

在这个例子中,data 数组的大小为 10 个元素,我们需要将其对齐到 8 字节边界。DATA_ALIGN_DOWN 宏返回 data 数组中第一个元素的地址,即 &data[0]。由于我们需要 8 字节对齐,我们将返回的地址减去 4 个字节,以获得正确的对齐地址:

int *aligned_data = &data[0] - 4;

2 原理

2.1 具体怎么做?

一般来说,环形缓冲区进行读写操作,最少需要4个信息:

        🐋 ① 在内存中的实际开始位置

        🐋 ② 在内存中的实际结束位置

        🐋 ③ 在缓冲区中进行写操作时的写索引值

        🐋 ④ 在缓冲区中进行读操作时的读索引值

4个信息的作用:

        🐳 ① 缓冲区的开始位置和缓冲区的结束位置(或空间大小)实际上定义了环形缓冲区的实际逻辑空间和大小

        🐳 ② 读索引和写索引标记了缓冲区进行读操作和写操作时的具体位置

图1:当环形缓冲区为空时,读索引和写索引指向相同的位置(因为是环形缓冲区,可以出现在任何位置);

图2:当向缓冲区写入一个元素时,元素A被写入到写索引当前所指向的位置,接着写索引加1,指向下一个位置;

图3:当再写入一个元素B时,元素B继续被写入到写索引当前所指向的位置,接着写索引加1,指向下一个位置;

图4:当接着写入C、D、E、F、G五个元素后,缓冲区就满了,这时写索引和读索引指向同一个位置(和缓冲区为空时一样);

 图5:当从缓冲区中读出一个元素A时,读索引当前所在位置的元素被读出,接着读索引加1,指向下一个位置;

图6:继续读出元素B时,还是读索引当前所在位置的元素被读出,接着读索引加1,指向下一个位置

2.2 环形缓冲区和镜像指示位

2.2.1 在缓冲区满的时候写数据,有两种策略可以使用

缓冲区变满在环形缓冲区(ring buffer)中会实际发生,一般会有两种处理策略:

        🐞① 覆盖老数据

        🐞② 抛出“异常”

两种策略如何选择要结合具体的应用场景:

        🦥选择第一种策略:如音/视频流中,丢掉一些数据不要紧

        🦝选择第二种策略:在任务间通信的,要严格保证数据正确传输

2.2.2 读数据时,一定要读出缓冲区中最老的数据 

        🦉① 环形缓冲区(ring buffer)也是FIFO类型的数据结构,需要满足先进先出的原则

        🦉② 写就相当于进,读就相当于出

        🦉③ 读数据时,一定要保证读最老的数据

一般读数据时,不会有问题。但有一种场景需要小心,看如下两张图

 图一所示:环形缓冲区的大小为七,缓冲区中已经存储了五个元素,7,8,9,3,4

 图二所示:再向缓冲区中写入三个元素A,B,C,因为剩余空间为2 了,所以要写入这三个元素肯定会覆盖掉一个元素。也就是元素7被覆盖为元素C,读索引不再停留在元素7处,而是在元素8处。此时元素8变为最老的元素。

这个例子表明:当缓冲区是满的,继续写入元素(覆盖),除了写索引要变,读索引也要跟着变,保证读索引一定是指向缓冲区中最老的元素。

【举个例子】:

 🐞 这是一个空间大小为7的环形缓冲区,其中底部的单线箭头表示“头尾相连”形成一个环形地址空间

 🐞 将1写入缓冲区中部(对于环形缓冲区来说,最初的写入位置在哪里是无关紧要的)

🐞 再写入两个元素,分别是23,这两个元素被追加到1之后

 🐞 读出两个元素,那么环形缓冲区中最老的两个元素将被读出(先进先出原则)

🐞 紧接着,向缓冲区中写入六个元素4、5、6、7、8、9,这时缓冲区会被装满

🐞 如果缓冲区是满的,又要写入新的数据,这时有两种策略:一种是覆盖掉最老的数据,也就是将老数据丢掉;另一种是返回错误码或者抛出异常。来看策略一,例如,这时写入两个元素AB,就会覆盖掉34

🐞 读出两个元素,就不是34而是5656这时最老),34已经被AB覆盖掉

实现环形缓冲区(ring buffer)需要注意的点:

        🦝1.在缓冲区满的时候写数据,有两种策略可以使用: ① 覆盖掉老数据 ② 抛出异常

        🦝2.读数据时,一定要读出缓冲区中最老的数据(缓冲区中数据满足FIFO特性)

        🦝3.判断缓冲区是满的

        🦝4.实现一个线性地址空间的循环读写

2.2.3 怎样来判断缓冲区是满的

缓冲区是满还是空,都有可能出现读索引与写索引指向同一位置 

如何判断缓冲区是满还是空,在环形缓冲区中是一个重点问题,本文重点讲解镜像指示位

镜像指示位:缓冲区的长度如果是n,逻辑地址空间则为0至n-1;那么,规定n至2n-1为镜像逻辑地址空间。本策略规定读写指针的地址空间为0至2n-1,其中低半部分对应于常规的逻辑地址空间,高半部分对应于镜像逻辑地址空间。当指针值大于等于2n时,使其折返(wrapped)到ptr-2n。使用一位表示写指针或读指针是否进入了虚拟的镜像存储区:置位表示进入,不置位表示没进入还在基本存储区。

        在读写指针的值相同情况下,如果二者的指示位相同,说明缓冲区为空;如果二者的指示位不同,说明缓冲区为满。这种方法优点是测试缓冲区满/空很简单;不需要做取余数操作;读写线程可以分别设计专用算法策略,能实现精致的并发控制。缺点是读写指针各需要额外的一位作为指示位。

        如果缓冲区长度是2的幂,则本方法可以省略镜像指示位。如果读写指针的值相等,则缓冲区为空;如果读写指针相差n,则缓冲区为满,这可以用条件表达式(写指针==(读指针异或缓冲区长度))来判断。

----(来自百度百科)

 【总结】

>>镜像指示位(有关读写指针)

🍇如何判别缓冲区判空/满?

        🦝① 在读写指针的值相同情况下,如果二者的指示位相同,说明缓冲区为空;

        🦝② 如果二者的指示位不同,说明缓冲区为满

逻辑:若写指针超出了读指针n位,在普通情况下,读写指针重合,但在这种情况下,写指针在镜像空间的读指针+n位,不和读指针重合

🍓优点:

        🦝① 测试缓冲区满/空很简单;不需要做取余数操作;

        🦝② 读写线程可以分别设计专用算法策略,能实现精致的并发控制

🍄缺点:读写指针各需要额外的一位作为指示位

🐞 前提:如果缓冲区长度是2的幂,则本方法可以省略镜像指示位

        🦝① 如果读写指针的值相等,则缓冲区为空;

        🦝② 如果读写指针相差n,则缓冲区为满;

        🦝③ 可用 (写指针 == (读指针 异或 缓冲区长度))来判断

                写指针:            1011

                读指针:            0011

                缓冲区长度:    1000

                异或:               1011

>>区别 读写指针 和 读写索引

        🦝① 读写指针的范围是[0,2n-1]

        🦝② 读索引和写索引的范围是[0,n-1],其必须和缓冲区的实际逻辑空间一致

        🦝③ 读指针和读索引,写指针和写索引的转换关系:

                读索引 = 读指针 % 缓冲区长度

                写索引 = 写指针 % 缓冲区长度

其中%号,是求余运算符,但是如果缓冲区长度是2的幂,那么求余运算可以等价的转换为如下的按位与运算:

                读索引 = 读指针&(缓冲区长度 - 1)

                写索引 = 写指针&(缓冲区长度 - 1)

🍇思考:为什么环形缓冲区大小必须是2的幂?

使用2的幂的优势:

size = 32;

bin(size) => '00100000'

mask = size - 1;

bin(mask)=>'00011111'

使用按位and应用此掩码,随着索引的增长,我们仅隔离包含0-31范围内的数字的位

index = 4
bin(index & mask)=>'00000100'  (4)

index = 32
bin(index & mask)=>'00000000'  (0)

index = 33
bin(index & mask)=>'00000001'  (1)

index = 64
bin(index & mask)=>'00000000'  (0)

index = 65
bin(index & mask)=>'00000001'  (1)

🍓【总结】这种方法不需要比较,不需要分支,并且是安全的(结果索引始终在范围内)。它还有一个额外的好处,那就是不会丢弃信息;但仍然保留了索引在逻辑上是65 的信息(事实证明这非常有用)

🍄 【效率分析】:按位与运算效率要比求余运算高得多,在Linux内核中将缓冲区长度扩展为2的幂长度随处可见,都是为了按位与操作代替求余操作。

🍄【归纳】为了判断缓冲区是否为空或者满,镜像指示位策略引入了两个布尔变量(指示位),来分别标记读指针或者写指针是否进入了镜像空间[n,2n-1],在读写指针的值相同情况下,如果二者的指示位相同,说明缓冲区为空;如果二者的指示位不同,说明缓冲区为满。但如果缓冲区的长度为2的幂,则可以省略镜像指示位。如果读写指针的值相等,则缓冲区为空;如果读写指针相差n,则缓冲区为满。

三种可能出现的情形:

情形一中,开始缓冲区中有两个元素1、2,接着继续写入A、B、C三个元素。就是说从写索引到缓冲区结束位置这一段空间能容纳全部所写入数据

情形二中,开始缓冲区有两个元素1、2,接着继续写入A、B、C三个元素。可以看出写索引到缓冲区结束位置还可以容纳2个元素,也就是说A、B两个元素可以写入从写索引到缓冲区结束位置这一段,而C只能回环到缓冲区的开头位置。就是说从写索引到缓冲区结束位置这一段空间无法全部容纳所写入数据,写索引需要回环到缓冲区开头,写入剩下的数据

情形三中,描述了该过程,开始缓冲区中有两个元素1、2,接着继续写入A、B、C、D、E、F六个元素。此时元素 1 被覆盖掉,写索引和读索引都指向元素 2 . 如果发生了元素覆盖,那缓冲区一定会变满,read_index和write_index会相等

2.2.4 完整代码

ringbuffer.cpp

#include <iostream>
#include <stdlib.h>
#include <memory.h>
#include <string.h>
#include <cmath>

#define DATA_ALIGN_SIZE	4
#define DATA_ALIGN_DOWN(size, align)      ((size) & ~((align) - 1))

using namespace std;
typedef struct ringbuff{
    char* buff;
    unsigned int size;
    unsigned int rdId;//读索引readIndex
    unsigned int wrId;//写索引writeIndex
    uint16_t read_mirror : 1;
    uint16_t write_mirror : 1;
}ringbuff;

void Init(ringbuff* rb,char* buff,unsigned int size) {
    memset(rb,0,sizeof(rb));
    rb->size = DATA_ALIGN_DOWN(size, DATA_ALIGN_SIZE);
    // rb->size = size;
    rb->buff = buff;
    rb->rdId = 0;
    rb->wrId = 0;
    rb->write_mirror = rb->read_mirror = 0;
}

#define RINGBUFF_EMPTY     0
#define RINGBUFF_FULL      1  
#define RINGBUFF_HALFFULL  2
// 获得缓冲区所剩空闲个数:缓冲区长度 - 缓冲区存放数据的空间个数 
#define rb_free_size(rb) ((rb)->size - rb_data_size(rb)) 

static inline int32_t rb_state_get(ringbuff* rb) {
    if(rb->rdId == rb->wrId) {
        if(rb->read_mirror == rb->write_mirror) 
            return RINGBUFF_EMPTY;
        else
            return RINGBUFF_FULL;
    }
    else 
        return RINGBUFF_HALFFULL;
}

// 获取缓冲区所存储数据长度
int16_t rb_data_size(ringbuff* rb) {
    switch (rb_state_get(rb))
    {
    case RINGBUFF_EMPTY:
        return 0;
    case RINGBUFF_FULL:
        return rb->size;
    case RINGBUFF_HALFFULL:
        if(rb->read_mirror == rb->write_mirror) {
            // return abs(int(rb->wrId - rb->rdId));
            return abs(int(rb->wrId - rb->rdId));
        } else {
            // return rb->size - rb->rdId - rb->wrId;
            if(rb->rdId > rb->wrId)
                return rb->size - (rb->rdId - rb->wrId);
            else 
                return rb->wrId - rb->rdId;
        }
    }

    // never reach here
    return rb->size;
};

void write(ringbuff* rb,char * buf,unsigned int strlen)
{
    unsigned int pos=rb->wrId;//记录我们写指针的偏移
    unsigned int tmpPos=pos;//记录偏移位置
    int flag = 0;
    unsigned int tlen = strlen;
    int empty = rb_free_size(rb);
    int data = rb_data_size(rb);
    
    
    while(pos+strlen>rb->size){//判断我们写的字节数是否大于整个buffer长度
        //cout<<"进入循环: "<<endl;
        memcpy(rb->buff+pos,buf,rb->size-pos);//将我们的内容直接拷贝到偏移位置
        buf +=(rb->size-pos); //获取要拷贝的buf地址
        strlen -=rb->size-pos;  //计算多出来的长度
        pos=0;
        flag = 1;
    }
    
    memcpy(rb->buff+pos,buf,strlen);//将多出来的长度的内容搬到基址开始,从我们的buf中继续写过来,实现循环
    rb->wrId=pos+strlen; //移动我们的写指针
    // 方法二
    rb->wrId = rb->wrId & (rb->size - 1);
    int mirrorFlag = 0;
    if(flag && !rb->write_mirror || (rb->write_mirror==1 && rb->read_mirror==1)) {
        mirrorFlag = 1;
    }
    if(flag && !pos && mirrorFlag|| rb->wrId == 0) {
        // cout<<"取反~"<<endl;
        if(rb->write_mirror==1 && rb->read_mirror==1) {
            rb->read_mirror = 0;
        }
        if(rb->write_mirror==0 && rb->read_mirror==0) {
            rb->write_mirror = ~rb->write_mirror;
        }
    }
    int modify_flag = 0;
    if(rb->wrId < rb->rdId && tlen<rb->size && empty!=rb->size){
        // cout<<"进入判断:~"<<endl;
        modify_flag = 1;
    }
	int t = rb->size-1-(tlen+rb->wrId);
	// cout<<"rb->size-1-(tlen+rb->wrId) : "<<t<<endl;
	if(modify_flag && t==0) {
		// cout<<"modify_flag: "<<modify_flag<<endl;
        // cout<<"打印:"<<tmpPos-rb->wrId<<endl;
        if(!(rb->wrId < rb->rdId && tlen <empty)) {
            rb->rdId = rb->wrId + t;
        }
		modify_flag = 0;
	}
    if(rb->wrId + (empty-tlen) == rb->rdId) {
        // cout<<"==="<<endl;
        modify_flag = 0;
    }
	if(rb->rdId < rb->wrId && rb->write_mirror || modify_flag)  {
        rb->rdId = rb->wrId;
    }
	
}

int read(ringbuff* rb,char* srcbuff,unsigned int len) {
    unsigned int pos = rb->rdId;
    unsigned int tlen = len;
    int flag = 0;
    // empty buffer 报错!!
    if(!rb_data_size(rb)) {
        cout<<"empty buffer"<<endl;
        return 0;
    }

    if(len > rb_data_size(rb)) {
        len = rb_data_size(rb);
        cout<<"已超出缓冲区大小,只可读取"<<len<<"个字节"<<endl;
    }

    while(pos + len > rb->size) {
        memcpy(srcbuff,rb->buff + pos,rb->size - pos);
        srcbuff += (rb->size - pos);
        len -= (rb->size - pos);
        pos = 0;
        flag = 1;
    }
    memcpy(srcbuff,rb->buff + pos,len);
    rb->rdId = pos + len;

    rb->rdId = rb->rdId & (rb->size - 1);
    if(flag && !pos || rb->rdId == 0) {
        rb->read_mirror = ~rb->read_mirror;
    }
    if(rb->rdId == rb->wrId) {
        rb->read_mirror = rb->write_mirror = 0;
    }
}

void display(ringbuff* rb) {
    for(int i=0;i<rb->size;i++){
        cout<<rb->buff[i];
    }
    cout<<endl;
}

void readprint(ringbuff* rb,int len) {
	cout<<endl;
    char buff[20] = "";
    read(rb, buff, len);
	int i = 0;
	cout<<"读出:";
	while(buff[i]!='\0') {
		cout<<buff[i++];
	}
	cout<<endl;
	cout<<"read_index: "<<rb->rdId<<endl;
	cout<<"write_index: "<<rb->wrId<<endl;
	cout<<"rb->read_mirror: "<<rb->read_mirror<<endl;    
	cout<<"rb->write_mirror: "<<rb->write_mirror<<endl;
	cout<<"rb_state_get: "<<rb_state_get(rb)<<endl;
	cout<<"rb_data_size: "<<rb_data_size(rb)<<endl;
	cout<<"rb_free_size: "<<rb_free_size(rb)<<endl;
	cout<<endl;
}

void writeprint(ringbuff* rb,char* p,int len) {
    cout<<"写入:";
    for(int i=0;i<len-1;i++) {
        cout<<*(p+i);
    }
    cout<<endl;
    write(rb,p,len-1);
    display(rb);
    cout<<"read_index: "<<rb->rdId<<endl;
    cout<<"write_index: "<<rb->wrId<<endl;
    cout<<"rb->read_mirror: "<<rb->read_mirror<<endl;    
    cout<<"rb->write_mirror: "<<rb->write_mirror<<endl;
    cout<<"rb_state_get: "<<rb_state_get(rb)<<endl;
    cout<<"rb_data_size: "<<rb_data_size(rb)<<endl;
    cout<<"rb_free_size: "<<rb_free_size(rb)<<endl;
	cout<<endl;
}

int main() {
    ringbuff* rb = (ringbuff*)malloc(sizeof(ringbuff));
    char init_str[8] = "0000000";
    int size = sizeof(init_str);
    Init(rb,init_str,size);
   
    // char p[] = "123456789A";
    char p[] = "123456789A12345678";
    // char p[] = "12345678";
    int len = sizeof(p) / sizeof(char);
	writeprint(rb,p,len);

	readprint(rb,7);

    readprint(rb,9);

    readprint(rb,9);

    char p1[] = "HEHEDA";
	len = sizeof(p1) / sizeof(char);
    writeprint(rb,p1,len);

    char p2[] = "Tom";
	len = sizeof(p2) / sizeof(char);
	writeprint(rb,p2,len);

    char p3[] = "abcdefgh";
	len = sizeof(p3) / sizeof(char);
	writeprint(rb,p3,len);

    readprint(rb,6);
    readprint(rb,3);

    char p4[] = "abcdefgh";
	len = sizeof(p4) / sizeof(char);
	writeprint(rb,p4,len);

    char p5[] = "kaixinma";
	len = sizeof(p5) / sizeof(char);
	writeprint(rb,p5,len);

    readprint(rb,6);

    char p6[] = "handou";
	len = sizeof(p6) / sizeof(char);
    writeprint(rb,p6,len);

    readprint(rb,6);

    char p7[] = "xiaohua";
	len = sizeof(p7) / sizeof(char);
	writeprint(rb,p7,len);

    char p8[] = "houlai";
	len = sizeof(p8) / sizeof(char);
    writeprint(rb,p8,len);

    char p9[] = "small";
	len = sizeof(p9) / sizeof(char);
	writeprint(rb,p9,len);

    char p10[] = "bigboy";
	len = sizeof(p10) / sizeof(char);
	writeprint(rb,p10,len);

    char p11[] = "yaoguai";
	len = sizeof(p11) / sizeof(char);
	writeprint(rb,p11,len);

    char p12[] = "halemu";
	len = sizeof(p12) / sizeof(char);
	writeprint(rb,p12,len);

	readprint(rb,7);

    char p13[] = "kabola";
	len = sizeof(p13) / sizeof(char);
	writeprint(rb,p13,len);

    char p14[] = "skuraxi";
	len = sizeof(p14) / sizeof(char);
	writeprint(rb,p14,len);

    char p15[] = "xiaola";
	len = sizeof(p15) / sizeof(char);
	writeprint(rb,p15,len);

    readprint(rb,7);

    char p16[] = "bobonihao";
	len = sizeof(p16) / sizeof(char);
    writeprint(rb,p16,len);

    char p17[] = "sha";
	len = sizeof(p17) / sizeof(char);
    writeprint(rb,p17,len);

    readprint(rb,6);

    char p18[] = "guale";
	len = sizeof(p18) / sizeof(char);
    writeprint(rb,p18,len);

    readprint(rb, 8);

    char p19[] = "chaoshan";
    len = sizeof(p19) / sizeof(char);
    writeprint(rb,p19,len);

    char p20[] = "huabao";
    len = sizeof(p20) / sizeof(char);
    writeprint(rb,p20,len);
}

测试过程:

heheda@heheda:~/Linux/RT-Thread$ g++ ringbuffer.cpp -o app
heheda@heheda:~/Linux/RT-Thread$ ./app
写入:123456789A12345678
78123456
read_index: 2
write_index: 2
rb->read_mirror: 0
rb->write_mirror: 1
rb_state_get: 1
rb_data_size: 8
rb_free_size: 0


读出:1234567
read_index: 1
write_index: 2
rb->read_mirror: 1
rb->write_mirror: 1
rb_state_get: 2
rb_data_size: 1
rb_free_size: 7


已超出缓冲区大小,只可读取1个字节
读出:8
read_index: 2
write_index: 2
rb->read_mirror: 0
rb->write_mirror: 0
rb_state_get: 0
rb_data_size: 0
rb_free_size: 8


empty buffer
读出:
read_index: 2
write_index: 2
rb->read_mirror: 0
rb->write_mirror: 0
rb_state_get: 0
rb_data_size: 0
rb_free_size: 8

写入:HEHEDA
78HEHEDA
read_index: 2
write_index: 0
rb->read_mirror: 0
rb->write_mirror: 1
rb_state_get: 2
rb_data_size: 6
rb_free_size: 2

写入:Tom
TomEHEDA
read_index: 3
write_index: 3
rb->read_mirror: 0
rb->write_mirror: 1
rb_state_get: 1
rb_data_size: 8
rb_free_size: 0

写入:abcdefgh
fghabcde
read_index: 3
write_index: 3
rb->read_mirror: 0
rb->write_mirror: 1
rb_state_get: 1
rb_data_size: 8
rb_free_size: 0


读出:abcdef
read_index: 1
write_index: 3
rb->read_mirror: 1
rb->write_mirror: 1
rb_state_get: 2
rb_data_size: 2
rb_free_size: 6


已超出缓冲区大小,只可读取2个字节
读出:gh
read_index: 3
write_index: 3
rb->read_mirror: 0
rb->write_mirror: 0
rb_state_get: 0
rb_data_size: 0
rb_free_size: 8

写入:abcdefgh
fghabcde
read_index: 3
write_index: 3
rb->read_mirror: 0
rb->write_mirror: 1
rb_state_get: 1
rb_data_size: 8
rb_free_size: 0

写入:kaixinma
nmakaixi
read_index: 3
write_index: 3
rb->read_mirror: 0
rb->write_mirror: 1
rb_state_get: 1
rb_data_size: 8
rb_free_size: 0


读出:kaixin
read_index: 1
write_index: 3
rb->read_mirror: 1
rb->write_mirror: 1
rb_state_get: 2
rb_data_size: 2
rb_free_size: 6

写入:handou
umahando
read_index: 1
write_index: 1
rb->read_mirror: 0
rb->write_mirror: 1
rb_state_get: 1
rb_data_size: 8
rb_free_size: 0


读出:mahand
read_index: 7
write_index: 1
rb->read_mirror: 0
rb->write_mirror: 1
rb_state_get: 2
rb_data_size: 2
rb_free_size: 6

写入:xiaohua
uxiaohua
read_index: 0
write_index: 0
rb->read_mirror: 0
rb->write_mirror: 1
rb_state_get: 1
rb_data_size: 8
rb_free_size: 0

写入:houlai
houlaiua
read_index: 6
write_index: 6
rb->read_mirror: 0
rb->write_mirror: 1
rb_state_get: 1
rb_data_size: 8
rb_free_size: 0

写入:small
alllaism
read_index: 3
write_index: 3
rb->read_mirror: 0
rb->write_mirror: 1
rb_state_get: 1
rb_data_size: 8
rb_free_size: 0

写入:bigboy
yllbigbo
read_index: 1
write_index: 1
rb->read_mirror: 0
rb->write_mirror: 1
rb_state_get: 1
rb_data_size: 8
rb_free_size: 0

写入:yaoguai
yyaoguai
read_index: 0
write_index: 0
rb->read_mirror: 0
rb->write_mirror: 1
rb_state_get: 1
rb_data_size: 8
rb_free_size: 0

写入:halemu
halemuai
read_index: 6
write_index: 6
rb->read_mirror: 0
rb->write_mirror: 1
rb_state_get: 1
rb_data_size: 8
rb_free_size: 0


读出:aihalem
read_index: 5
write_index: 6
rb->read_mirror: 1
rb->write_mirror: 1
rb_state_get: 2
rb_data_size: 1
rb_free_size: 7

写入:kabola
bolamuka
read_index: 5
write_index: 4
rb->read_mirror: 0
rb->write_mirror: 1
rb_state_get: 2
rb_data_size: 7
rb_free_size: 1

写入:skuraxi
axiaskur
read_index: 3
write_index: 3
rb->read_mirror: 0
rb->write_mirror: 1
rb_state_get: 1
rb_data_size: 8
rb_free_size: 0

写入:xiaola
axixiaol
read_index: 1
write_index: 1
rb->read_mirror: 0
rb->write_mirror: 1
rb_state_get: 1
rb_data_size: 8
rb_free_size: 0


读出:xixiaol
read_index: 0
write_index: 1
rb->read_mirror: 1
rb->write_mirror: 1
rb_state_get: 2
rb_data_size: 1
rb_free_size: 7

写入:bobonihao
aoobonih
read_index: 2
write_index: 2
rb->read_mirror: 0
rb->write_mirror: 1
rb_state_get: 1
rb_data_size: 8
rb_free_size: 0

写入:sha
aoshanih
read_index: 5
write_index: 5
rb->read_mirror: 0
rb->write_mirror: 1
rb_state_get: 1
rb_data_size: 8
rb_free_size: 0


读出:nihaos
read_index: 3
write_index: 5
rb->read_mirror: 1
rb->write_mirror: 1
rb_state_get: 2
rb_data_size: 2
rb_free_size: 6

写入:guale
leshagua
read_index: 3
write_index: 2
rb->read_mirror: 0
rb->write_mirror: 1
rb_state_get: 2
rb_data_size: 7
rb_free_size: 1


已超出缓冲区大小,只可读取7个字节
读出:haguale
read_index: 2
write_index: 2
rb->read_mirror: 0
rb->write_mirror: 0
rb_state_get: 0
rb_data_size: 0
rb_free_size: 8

写入:chaoshan
anchaosh
read_index: 2
write_index: 2
rb->read_mirror: 0
rb->write_mirror: 1
rb_state_get: 1
rb_data_size: 8
rb_free_size: 0

写入:huabao
anhuabao
read_index: 0
write_index: 0
rb->read_mirror: 0
rb->write_mirror: 1
rb_state_get: 1
rb_data_size: 8
rb_free_size: 0

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/900462.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python可视化在量化交易中的应用(15)_Seaborn箱线图小提琴图

Seaborn中箱线图和小提琴图的绘制方法 箱线图和小提琴图常被用来观测数据的中位数、上下四分位数分布范围以及异常值的分布情况。 seaborn中绘制箱线图使用的是sns.boxplot()函数。 sns.boxplot(x,y,hue,data,order,hue_order,orient,color,palette,saturation0.75,width0.8,do…

C语言 功能型API --------------------strcat()

NAME strcat, strncat - concatenate two strings 头文件 SYNOPSIS #include <string.h> 函数原型&#xff1a; char *strcat(char *dest, const char *src); 功能&#xff1a; 在字符串dest的末尾将字符串src拼接上去 #include <stdio.h> #inc…

227、仿真-基于51单片机锅炉热电偶PT100铂电阻温度控制Proteus仿真设计(程序+Proteus仿真+原理图+流程图+元器件清单+配套资料等)

毕设帮助、开题指导、技术解答(有偿)见文未 目录 一、设计功能 二、Proteus仿真图 三、原理图 四、程序源码 资料包括&#xff1a; 需要完整的资料可以点击下面的名片加下我&#xff0c;找我要资源压缩包的百度网盘下载地址及提取码。 方案选择 单片机的选择 方案一&…

LlamaGPT -基于Llama 2的自托管类chatgpt聊天机器人

LlamaGPT一个自托管、离线、类似 ChatGPT 的聊天机器人&#xff0c;由 Llama 2 提供支持。100% 私密&#xff0c;不会有任何数据离开你的设备。 推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景 1、如何安装LlamaGPT LlamaGPT可以安装在任何x86或arm64系统上。 首先确保…

网络编程(TCP和UDP的基础模型)

一、TCP基础模型&#xff1a; tcp Ser&#xff1a; #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string.h> #include <head.h>#define PORT 88…

探索无限创造力的星辰大道,画出想象的浩瀚宇宙!-turtle

介绍 视频教程地址在此&#xff1a;https://www.bilibili.com/video/BV1Pm4y1H7Tb/ 大家好&#xff0c;欢迎来到本视频&#xff01;今天&#xff0c;我们将一同探索Python编程世界中的一个有趣而创意的库——Turtle库。无需专业绘画技能&#xff0c;你就可以轻松地用代码绘制…

docker的安装与基础使用

一.docker简介 1&#xff09;什么是docker Docker是一种用于构建、打包和运行应用程序的开源平台。它基于操作系统级虚拟化技术&#xff0c;可以将应用程序和其依赖的库、环境等资源打包到一个可移植的容器中&#xff0c;形成一个轻量级、独立的可执行单元。 开发者在本地编…

QT TLS initialization failed问题(已解决) QT基础入门【网络编程】openssl

问题: qt.network.ssl: QSslSocket::connectToHostEncrypted: TLS initialization failed 这个问题的出现主要是使用了https请求:HTTPS ≈ HTTP + SSL,即有了加密层的HTTP 所以Qt 组件库需要OpenSSL dll 文件支持HTTPS 解决: 1.加入以下两行代码获取QT是否支持opensll以…

【学会动态规划】单词拆分(24)

目录 动态规划怎么学&#xff1f; 1. 题目解析 2. 算法原理 1. 状态表示 2. 状态转移方程 3. 初始化 4. 填表顺序 5. 返回值 3. 代码编写 写在最后&#xff1a; 动态规划怎么学&#xff1f; 学习一个算法没有捷径&#xff0c;更何况是学习动态规划&#xff0c; 跟我…

向gitee推送代码

目录 一、Gitee创建仓库 二、将刚刚创建的仓库放到虚拟机上 2.1 https 方式克隆仓库 2.2 ssh的方式克隆仓库 三、本地开发&#xff0c;推送 3.1 查看是否有远程库 3.2 推送代码 3.3 查看是否推送成功 一、Gitee创建仓库 二、将刚刚创建的仓库放到虚拟机上 2.1 https 方式…

codesys和HMI通讯

codesys可视化有2种&#xff1a; 网页web // 类似于路由器管理那样&#xff0c;登录网页就能操作 本地HMI // 其他品牌的触摸屏 符号配置&#xff1a; 1 编译需要的变量 2 导出XML文件 3 触摸屏软件加载XML文件

kafka--kafka的基本概念-topic和partition

一、kafka的基本概念-topic和partition 1、topic &#xff08;主题 &#xff09; topic是逻辑概念 以Topic机制来对消息进行分类的&#xff0c;同一类消息属于同一个Topic&#xff0c;你可以将每个topic看成是一个消息队列。 生产者&#xff08;producer&#xff09;将消息发…

我只是用了个“笨”方法,一个月后不再惧怕英文文档

在日常工作中&#xff0c;尤其是程序员时时刻刻都会与英文打交道&#xff0c;虽然我们尽可能的在互联网和中文书籍中寻找我们需要的信息&#xff0c;但是&#xff0c;有时候总是不尽人意。对待翻译过来的文档或者书本可能有些定义依然无法明确理解&#xff0c;回到它原有的场景…

【二叉树前沿篇】树

【二叉树前沿篇】树 1 树的概念2. 树的相关概念3. 树的表示4. 树在实际中的运用&#xff08;表示文件系统的目录树结构&#xff09; 1 树的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是…

Django模型基础

文章目录 一、models字段类型概述属性命名限制使用方式逻辑删除和物理删除常用字段类型 二、常用字段参数常用字段选项(通过字段选项&#xff0c;可以实现对字段的约束) 实践创建模型执行迁移命令 并 创建超级用户登录admin后台添加文件和图片字段定义模型字段和约束及在Admin后…

C#生产流程控制(串行,并行混合执行)

开源框架CsGo https://gitee.com/hamasm/CsGo?_fromgitee_search 文档资料&#xff1a; https://blog.csdn.net/aa2528877987/article/details/132139337 实现效果 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37…

大语言模型之四-LlaMA-2从模型到应用

最近开源大语言模型LlaMA-2火出圈&#xff0c;从huggingface的Open LLM Leaderboard开源大语言模型排行榜可以看到LlaMA-2还是非常有潜力的开源商用大语言模型之一&#xff0c;相比InstructGPT&#xff0c;LlaMA-2在数据质量、培训技术、能力评估、安全评估和责任发布方面进行了…

图像处理常见的两种拉流方式

传统算法或者深度学习在进行图像处理之前&#xff0c;总是会首先进行图像的采集&#xff0c;也就是所谓的拉流。解决拉流的方式有两种&#xff0c;一个是直接使用opencv进行取流&#xff0c;另一个是使用ffmpeg进行取流&#xff0c;如下分别介绍这两种方式进行拉流处理。 1、o…

基于深度学习创建-表情符号--附源码

表情符号深度学习概述 如今,我们使用多种表情符号或头像来表达我们的心情或感受。它们充当人类的非语言线索。它们成为情感识别、在线聊天、品牌情感、产品评论等的关键部分。针对表情符号驱动的故事讲述的数据科学研究不断增加。 从图像中检测人类情绪非常流行,这可能是由…

【ROS】参数服务器--理论模型与参数操作(C++)

一、概念介绍 参数服务器在ROS中主要用于实现不同节点之间的数据共享。参数服务器相当于是独立于所有节点的一个公共容器&#xff0c;可以将数据存储在该容器中&#xff0c;被不同的节点调用&#xff0c;当然不同的节点也可以往其中存储数据。 作用&#xff1a;存储一些多节点…