stream buffering in C (fmemopen)

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

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

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

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

以下是一個簡單的範例

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
#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的行為影響,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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長度指定為寫入的資料長度