環形緩沖區c語言
A. c語言中,什麼是緩沖文件系統和文件緩沖區
目前c語言使用的文件系統分為緩沖文件系統(標准i
/
o)和非緩沖文件系統(系統i
/
o)。緩沖文件系統的特點是:在內存開辟一個「緩沖區」,為程序中的每一個文件使用,當執
行讀文件的操作時,從磁碟文件將數據先讀入內存「緩沖區」,裝滿後再從內存「緩沖區」依此讀入接收的變數。執行寫文件的操作時,先將數據寫入內存「緩沖區」,待內存「緩沖區」裝滿後再寫入文件。
因此當程序運行時雖然進行了寫數據操作,但是如果寫入的數據沒有裝滿內存中的緩沖區,就不會將數據寫入到磁碟文件中。當程序運行結束後,系統就會將緩沖區中的數據寫入到磁碟上的文件中,因此就可以看到文件中的內容。
常用的fopen命令就會使用文件讀寫緩沖區,fclose命令關閉文件,把緩沖區中的內容寫入磁碟上的文件中。詳見:
http://study.qqcf.com/web/171/19812.htm
B. 數據結構之環形緩沖器
你有過這樣的經歷嗎?當你打開你的電腦,就因為輸入你的密碼缺導致出現了一個卡頓。這個卡頓的出現,主要是因為電腦處理器醒來發現它需要開始讀取鍵盤上的迷你處理器。但是,這些信息到底是如何存儲在那麼小的鍵盤內存空間里呢?這個就是圓形緩沖器作用的場景。
環形緩沖器 是一種特定的 隊列 。所以它也被稱作為 圓形隊列 。如果你對於隊列不太熟悉的話,你從名字上來看你至少能反映出它是一種直線的隊伍(如同大家排隊去上衛生間)。這個隊列里是 先進先出(FIFO) 。所以意味著,第一個進入隊伍的人,會第一個出隊。那麼到底環形緩沖器是如何與眾不同的呢?
環形緩沖區通常儲存數據在一個定長的數組里(array)。因此只要長度確定以後,它通過兩個指針分別指向這個隊列的隊尾和隊首的位置,來追蹤隊伍的情況。只需要根據隊伍的新元素的增加和減少,相應的向前移動指針。而無需去動態地去操作這個數組。舉個例子,隊首被推出了隊列,那麼其他隊伍里的元素都需要向前移動一位。這種操作是比較低效的。所以環形緩沖區避免了這種操作。因為這個環形隊列的實現是數組,所以只能通過添加新元素到隊首或者隊尾。假如需要添加元素到隊列的中間,則可能需要使用鏈表來實現。
一般來說,這個隊列裡面沒有任何元素的時候和隊列已經滿的時候,頭指針和尾指針是 指向同一個位置 的。
當元素被添加到隊列里時,頭指針向前移動一此。當元素從隊列里刪除時,尾指針同理。但是尾指針永遠都不應該跳過頭指針,因為你生產者永遠排在消費者前面,或者當隊列為空時兩個指針指向同一個元素。當指針移動到數組的末尾位置,它將重新跳轉回數組的起始位置。並且 頭指針和寫指針之間是線程安全的 。假如有多個消費者和生產者公用指針,則需要加鎖來保證線程安全。
緩沖區是滿、或是空,都有可能出現讀指針與寫指針指向同一位置。有多種策略用於檢測緩沖區是滿、或是空。
1. 總是保持一個存儲單元為空緩沖區中總是有一個存儲單元保持未使用狀態。緩沖區最多存入(size-1)。個數據。如果讀寫指針指向同一位置,則緩沖區為空。如果寫指針位於讀指針的相鄰後一個位置,則緩沖區為滿。這種策略的優點是簡單、粗暴;缺點是語義上實際可存數據量與緩沖區容量不一致,測試緩沖區是否滿需要做取余數計算。
2. 使用數據計數這種策略。不使用顯式的寫指針,而是保持著緩沖區內存儲的數據的計數。因此測試緩沖區是空是滿非常簡單;對性能影響可以忽略。缺點是讀寫操作都需要修改這個存儲數據計數,對於多線程訪問緩沖區需要並發控制。
3. 鏡像指示位。緩沖區的長度如果是n,邏輯地址空間則為0至n-1;那麼,規定n至2n-1為鏡像邏輯地址空間。本策略規定讀寫指針的地址空間為0至2n-1,其中低半部分對應於常規的邏輯地址空間,高半部分對應於鏡像邏輯地址空間。當指針值大於等於2n時,使其折返(wrapped)到ptr-2n。使用一位表示寫指針或讀指針是否進入了虛擬的鏡像存儲區:置位表示進入,不置位表示沒進入還在基本存儲區。
4. 在讀寫指針的值相同情況下,如果二者的指示位相同,說明緩沖區為空;如果二者的指示位不同,說明緩沖區為滿。這種方法優點是測試緩沖區滿/空很簡單;不需要做取余數操作;讀寫線程可以分別設計專用演算法策略,能實現精緻的並發控制。 缺點是讀寫指針各需要額外的一位作為指示位。
如果緩沖區長度是2的冪,則本方法可以省略鏡像指示位。如果讀寫指針的值相等,則緩沖區為空;如果讀寫指針相差n,則緩沖區為滿,這可以用條件表達式(寫指針 == (讀指針異或緩沖區長度))來判斷。
5. 讀/寫計數用。兩個有符號整型變數分別保存寫入、讀出緩沖區的數據數量。其差值就是緩沖區中尚未被處理的有效數據的數量。這種方法的優點是讀線程、寫線程互不幹擾;缺點是需要額外兩個變數。
6. 使用一位記錄最後一次操作是讀還是寫。讀寫指針值相等情況下,如果最後一次操作為寫入,那麼緩沖區是滿的;如果最後一次操作為讀出,那麼緩沖區是空。這種策略的缺點是讀寫操作共享一個標志位,多線程時需要並發控制。
Reference:
https://en.wikipedia.org/wiki/Circular_buffer
C. 環形緩沖區
本質上是一個數組,但是它又跟普通的數組不太一樣,普通的數組只有一個指針,移動下標是index+1的方式;
而環形緩沖區有兩個指針:
head指針:表示讀取數據的指針;
tail指針:表示寫數據的指針。
它是如何實現環形讀寫的呢?
通過對讀寫指針的移動進行取模(求余)運算實現的。
對於讀指針,如果當前位置有數據可讀,那麼讀取後指針移動,移動方式為:(head+1)%size。
對於寫指針,如果當前位置可寫入數據,那麼寫入數據後指針移動,移動方式為:(tail+1)%size。
通過求余的計算方式,每當指針的值達到size-1時,總能通過求余的方式回到0的位置,而後再進行相應的讀寫操作,指針再繼續按照上述方式進行移動。
環形緩沖區主要用於數據讀寫,相對應的有兩個重要的判斷條件:
1、緩沖區為空不能讀;
2、緩沖區已滿不能寫。
D. c語言中多線程讀寫同一個環形緩沖區的實現
#include <stdio.h>
#include <windows.h>
#include <process.h>
unsigned __stdcall ThreadWrite(void* param);
unsigned __stdcall ThreadRead(void* param);
int WriteSeque = 0;
int ReadSeque = 0;
int RingBuf[4];
void main()
{
HANDLE htw = (HANDLE)_beginthreadex(NULL, 0, ThreadWrite, NULL, 0, 0);
HANDLE htr = (HANDLE)_beginthreadex(NULL, 0, ThreadRead, NULL, 0, 0);
CloseHandle(htw);
CloseHandle(htr);
while (1)
{
if (WriteSeque >= 100)
{
break;
}
}
printf("Quit\n");
}
unsigned __stdcall ThreadWrite(void* param)
{
int i = 0;
while (1)
{
if (WriteSeque >= ReadSeque && WriteSeque- ReadSeque < 4)
{
RingBuf[WriteSeque%4] = i;
printf("Write:%d\n", i);
i++;
WriteSeque++;
Sleep(50);
}
}
}
unsigned __stdcall ThreadRead(void* param)
{
while (1)
{
if (ReadSeque < WriteSeque)
{
printf("Read:%d\n", RingBuf[ReadSeque%4]);
ReadSeque++;
Sleep(100);
}
}
}
為了讓你看到效果,讀寫線程的休眠時間略有不同。
E. C語言里如何設置緩沖區
將數據以鏈表形式順序存放,新數據總是放在表尾,待處理的數據總是在頭結點下的第一個結點,處理完畢則釋放空間。
#define BufferSize 1024 // 合適的大小你知道的
typedef struct node {
char *buffer;
struct node *next;
}*linkList;
LinkList *InitList() {
LinkList *head;
head = (char *)malloc(size(node));
head->next = NULL:
return head;
}
void AddData(LinkList *head, char *data) { // 將新數據添加到表尾
LinkList *p = head;
LinkList *anode = (char *)malloc(size(node));
anode->buffer = (char *)malloc(size(BufferSize));
strncpy(anode->buffer,data,BufferSize);
anode->next = NULL:
while(p->next) p = p->next;
p->next = anode;
}
void DealData(LinkList *head) {
LinkList *p = head->next;
if(p) {
head->next = p->next;
// p->buffer指向的數據待處理
free(p->buffer); // 處理完畢,釋放空間
free(p);
}
}
F. C語言緩沖區在哪裡
緩沖區具體在哪裡是與操作系統、編譯器相關的
以VC++為例。察看getchar的源代碼(src\fgetchar.c),有:
int
__cdecl
_fgetchar
(void){
return(getc(stdin));
}
#undef
getchar
int
__cdecl
getchar
(void){
return
_fgetchar();
}
可見getchar()相當於getc(stdin)
繼續察看getc(src\fgetc.c),有一段(為便於閱讀,有刪減):
int
__cdecl
getc
(FILE
*stream){
int
retval;
_ASSERTE(stream
!=
NULL);
_lock_str(stream);
__try
{
retval
=
_getc_lk(stream);
}
__finally
{
_unlock_str(stream);
}
return(retval);
}
這段代碼里_lock_str其實是通過Win32
API提供的臨街區來鎖住文件
接收用戶輸入發生在_getc_lk,_getc_lk宏調用_filbuf。_filbuf在_filbuf.c中可以查看,這段代碼比較長,就不貼出來了
_filbuf主要是調用了_read(_fileno(stream),
stream->_base,
stream->_bufsiz)
而_read最終則是調用了Win32API
ReadFile,以下是用WinDbg輸出的getchar的調用棧:
#
ChildEBP
RetAddr
00
0012fe6c
0040a4e7
kernel32!ReadFile
01
0012fea8
0040a3b9
TestStruct!_read_lk+0x107
[read.c
@
146]
02
0012fec0
00403140
TestStruct!_read+0x69
[read.c
@
75]
03
0012fee8
00401290
TestStruct!_filbuf+0xd0
[_filbuf.c
@
127]
04
0012ff08
004012cc
TestStruct!fgetc+0x80
[fgetc.c
@
44]
05
0012ff14
0040103d
TestStruct!getc+0xc
[fgetc.c
@
56]
06
0012ff20
00401058
TestStruct!_fgetchar+0xd
[fgetchar.c
@
37]
07
0012ff28
0040101e
TestStruct!getchar+0x8
[fgetchar.c
@
47]
08
0012ff80
0040115c
TestStruct!main+0xe
[d:\my
programs\teststruct\ts.cpp
@
4]
09
0012ffc0
7c816fe7
TestStruct!mainCRTStartup+0xfc
[crt0.c
@
206]
0a
0012fff0
00000000
kernel32!BaseProcessStart+0x23
可見,getchar最終調用了ReadFile。關於ReadFile的原理以及緩沖區在哪裡,請你再提一個問我再回答
G. C語言緩沖區在哪裡
緩沖區具體在哪裡是與操作系統、編譯器相關的
以VC++為例。察看getchar的源代碼(src\fgetchar.c),有:
int __cdecl _fgetchar (void){
return(getc(stdin));
}
#undef getchar
int __cdecl getchar (void){
return _fgetchar();
}
可見getchar()相當於getc(stdin)
繼續察看getc(src\fgetc.c),有一段(為便於閱讀,有刪減):
int __cdecl getc (FILE *stream){
int retval;
_ASSERTE(stream != NULL);
_lock_str(stream);
__try {
retval = _getc_lk(stream);
}
__finally {
_unlock_str(stream);
}
return(retval);
}
這段代碼里_lock_str其實是通過Win32 API提供的臨街區來鎖住文件
接收用戶輸入發生在_getc_lk,_getc_lk宏調用_filbuf。_filbuf在_filbuf.c中可以查看,這段代碼比較長,就不貼出來了
_filbuf主要是調用了_read(_fileno(stream), stream->_base, stream->_bufsiz)
而_read最終則是調用了Win32API ReadFile,以下是用WinDbg輸出的getchar的調用棧:
# ChildEBP RetAddr
00 0012fe6c 0040a4e7 kernel32!ReadFile
01 0012fea8 0040a3b9 TestStruct!_read_lk+0x107 [read.c @ 146]
02 0012fec0 00403140 TestStruct!_read+0x69 [read.c @ 75]
03 0012fee8 00401290 TestStruct!_filbuf+0xd0 [_filbuf.c @ 127]
04 0012ff08 004012cc TestStruct!fgetc+0x80 [fgetc.c @ 44]
05 0012ff14 0040103d TestStruct!getc+0xc [fgetc.c @ 56]
06 0012ff20 00401058 TestStruct!_fgetchar+0xd [fgetchar.c @ 37]
07 0012ff28 0040101e TestStruct!getchar+0x8 [fgetchar.c @ 47]
08 0012ff80 0040115c TestStruct!main+0xe [d:\my programs\teststruct\ts.cpp @ 4]
09 0012ffc0 7c816fe7 TestStruct!mainCRTStartup+0xfc [crt0.c @ 206]
0a 0012fff0 00000000 kernel32!BaseProcessStart+0x23
可見,getchar最終調用了ReadFile。關於ReadFile的原理以及緩沖區在哪裡,請你再提一個問我再回答
H. C語言中,什麼是緩沖文件系統和文件緩沖區
文件緩沖區即系統在讀寫程序時在內存中開辟的數據源與數據目標中間的一個用於保存完整數據內容的緩沖區域。
目前c語言使用的文件系統分為緩沖文件系統(標准i
/
o)和非緩沖文件系統(系統i
/
o)。緩沖文件系統的特點是:在內存開辟一個「緩沖區」,為程序中的每一個文件使用,當執
行讀文件的操作時,從磁碟文件將數據先讀入內存「緩沖區」,裝滿後再從內存「緩沖區」依此讀入接收的變數。執行寫文件的操作時,先將數據寫入內存「緩沖區」,待內存「緩沖區」裝滿後再寫入文件。
因此當程序運行時雖然進行了寫數據操作,但是如果寫入的數據沒有裝滿內存中的緩沖區,就不會將數據寫入到磁碟文件中。當程序運行結束後,系統就會將緩沖區中的數據寫入到磁碟上的文件中,因此就可以看到文件中的內容。
I. C語言里如何設置緩沖區,
很簡單的定義一個數組,用兩個變數下標來指向頭和尾,新數據來尾++ 老數據處理完頭++ 兩個下標超過界限時從頭開始循環利用 中間要考慮緩存數據空和數據滿的情況
或者你動態分配空間,來一個信號分配空間,插入隊列鏈表,處理完一個出隊列,釋放空間
J. 怎麼計算環形緩沖區長度
兩個函數未經調試
#defineMAXLEN200
intBuff[MAXLEN];
int*Ptr_W=Buff;//寫指針初始化為Buff[0]
int*Ptr_R=NULL;//讀指針初始化為NULL
//讀數據。成功讀出時,返回1,x存放讀出的結果。不成功返回0,x的內容不可用
intReadData(intBuff[],int*x){
if(Ptr_R==NULL||Ptr_R>=Ptr_W)return0;
*x=*Ptr_R;
Ptr_R+=Buff+(Ptr_R-Buff+1)%MAXLEN;
return1;
}
//寫數據。成功寫入時,返回。不成功返回0
intWriteData(intBuff[],intx){
if(Ptr_R==NULL){//初次寫入
Ptr_R=Buff;//初次寫入時,附帶完善讀指針的初始化操作
*Ptr_W++=x;
return1;
}
if(Ptr_W==Ptr_R)return0;//緩沖區滿
*Ptr_W=x;
Ptr_W+=Buff+(Ptr_W-Buff+1)%MAXLEN;
return1;
}