stream buffering in C (fmemopen)

C++中方便的stream提供了流式IO,如cin、cout、getline等

C中也有同樣功能的函式,透過fprintf、fscanf、fgets、fgetc達成

當流式IO不僅用於檔案或stdio,而可能用於pipe、socket時

POSIX Standard (200809L) 提供了一組方法將一塊記憶體視為檔案來使用

以下是一個簡單的範例

#include <stdio.h>
#include <inttypes.h>

int main()
{
char buf[256] = {};

FILE *buffer = fmemopen(buf, sizeof(buf), "w");

fprintf(buffer, "123");
fprintf(buffer, "456");
fprintf(buffer, "789");
fflush(buffer);
int len = ftell(buffer);
fclose(buffer);

printf("len = %d\n", len);

buffer = fmemopen(buf, len, "r");

// atoll
int64_t n = 0;
int ch = 0;
while( EOF != (ch = fgetc(buffer)) )
{
n = n*10 + ch - '0';
}

printf("%lld\n", n);
fclose(buffer);

return 0;
}

與一般檔案有差別的是,開啟模式使用讀+寫會產生令人困惑的行為

原因是stream內部會維護一個目前位置,而讀寫共用同一個位置

假設今天先寫入10個字元,位置被移動到11,下次使用一個讀取函式,會從11開始讀取

因此混合讀寫必須使用fseek ftell rewind來重設位置

當傳入的buf為NULL時,fmemopen內部會取得一塊buffer來操作,在fclose時關閉

使用者不能存取這塊空間,需要存取則改用open_memstream

另外一點重要差異是,判斷檔案的EOF是根據傳入的__size__,而非內容的null byte

使用fflush也不能手動設定EOF位置,fflush的效用只有加上null byte

下面這段code演示了EOF的行為影響,

int main()
{
FILE* buffer = fmemopen(NULL, 1<<9, "w+");
fprintf(buffer, "GET / ");
fprintf(buffer, "HTTP/1.1\n");
fprintf(buffer, "Host: www.google.com.tw\n");
fprintf(buffer, "Agent: ");
fprintf(buffer, "Curl\n");
fflush(buffer);
rewind(buffer);

char line[64] = {};
while( fgets(line, 64, buffer) )
printf("Get: %s\n", line);

fclose(buffer);

return 0;
}

以下是輸出

Get: GET / HTTP/1.1

Get: Host: www.google.com.tw

Get: Agent: Curl

Get:
Get:
Get:
Get:
Get:
Get:
Get:
Get:

直觀的看法可能會認為只讀到前3行資料

但實際上還多印了8行,內容都是64個\0 (w+將其truncated)

欲限制這種行為只能在open時的len長度指定為寫入的資料長度