# printf を使わずに標準入出力
こんにちは、@jabelic (opens new window)です。 本稿はNCC Advent Calendar 2021 (opens new window) の 2 日目の記事です。
C 言語を勉強するときにおそらく一番最初に使うであろう標準の関数はprintf
だと思います。さらに標準入力をするにはscanf
を使うと思います。
printf
やscanf
を使うには通常、stdio.h
と言うヘッダファイルを include しますが、これは非常にたくさんの機能を抱えています。詳しくは UNIX の man ページを見て欲しいです。当たり前ですが、標準入出力を行うだけであればstdio.h
を include しなくても実装可能です。
そこでprintf
の代わりにread
システムコールとwrite
システムコールを呼んで、文字列を標準出力するmyprint
と文字列を標準入力するmyscan
を実装します。
WARNING
システムコールは結構寡黙で、エラーが起きたことに気づけないことが多いので本来ならエラーハンドリングすべきですが、以下では省略しています。
# myprint
#include <sys/types.h>
#include <unistd.h>
int myprint(char *arguments)
{
write(0, arguments, strlen(arguments));
return 0;
}
# Description
write()
は、buf が指すバッファから、ディスクリプタ fildes が参照するオブジェクトに nbyte のデータを書き込もうとします。
ssize_t
write(int fildes, const void *buf, size_t nbyte);
返り値は ssize_t 型の 0-255 の数値であり、エラーであれば負の値を返します。 第一引数は書き込み先を指定します。ファイルに書き込みたい場合は open()の返り値を指定しますが、今回は stdout に出力したいので、第一引数には数値 1 を指定します。
名前 | 標準入出力 | ファイルディスクリプタ |
---|---|---|
標準入力 | stdin | 0 |
標準出力 | stdout | 1 |
標準エラー | stderr | 2 |
第二引数には書き込む文字列の先頭のポインタを当てます。第三引数にはそこから何 byte までのデータを書き込むかを指定します。
# myscan
#include <sys/types.h>
#include <unistd.h> // read, write
#include <sys/uio.h> // read, writev
#include <inttypes.h> // u_int8_t
#include <stdlib.h> // calloc
#include <string.h> // strlen
const ssize_t BUF_SIZE = 8192;
typedef struct scan
{
char *array;
ssize_t size;
} scan;
scan myscan(char *input)
{
u_int8_t buf[BUF_SIZE]; // BUF_SIZE byte. ここに書き込む.
// typedef unsigned char u_int8_t
// unsigned 8bit int data, 0~255.
ssize_t nread; // バイト数あるいはエラー発生通知を返す関数の型. エラーなら負の数を返す.
myprint(input);
nread = read(0, buf, sizeof buf); // 読み込んだbyte数を返す
scan scan_obj;
char *returns = calloc(1, sizeof(nread));
for (int i = 0; i < nread; i++)
{
returns[i] = buf[i];
}
scan_obj.array = returns;
scan_obj.size = nread;
return scan_obj;
}
# Description
myscan では read 関数を使います。
- まず buf 配列を用意して、読み込んだ文字列の格納先を先に確保します。一般にバッファと言います。
- そして引数として受け取った文字列 input を myprint 関数で出力し、その後に標準入力を行います。
read
関数での標準入力は read 関数の第一引数(ディスクリプタ)に 0 を指定します。stdin から読み取った値は第二引数で渡すバッファに書き込まれます。バッファは十分なメモリ容量を確保している必要があるので、ここではconst ssize_t BUF_SIZE = 8192;
としています。2 バイト文字では 4096 文字分の容量があるはずです。- その後、読み取った文字列とその長さを返すようにしています。
# sample usage:
以下のように呼び出します。
int main(int argc, char **argv)
{
scan scans = myscan("scan: ");
myprint(scans.array);
return 0;
}
ソースコードは: advent-calendar2021/main.c at master · jabelic/advent-calendar2021 (opens new window)
終わり。
# References
- C 言語の printf/scanf を自作して再現してみる - のむログ (opens new window)
- write - システムコールの説明 - Linux コマンド集 一覧表 (opens new window)
- プログラムの入出力 - C 言語入門 (opens new window)
- 標準入出力(2) | LinuxC (opens new window)
- C 言語: write(2)の正しい使い方 | Kazuki Ohta's Space (opens new window)
- libc とは何ぞや ? - たなか としひさ (opens new window)
- C 言語システムコール-write CapmNetwork (opens new window)
- C 言語システムコール-read CapmNetwork (opens new window)
- ライブコーディング cat コマンド - YouTube (opens new window)