# printf を使わずに標準入出力

こんにちは、@jabelic (opens new window)です。 本稿はNCC Advent Calendar 2021 (opens new window) の 2 日目の記事です。

C 言語を勉強するときにおそらく一番最初に使うであろう標準の関数はprintfだと思います。さらに標準入力をするにはscanfを使うと思います。 printfscanfを使うには通常、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

Last Updated: 12/6/2021, 7:41:52 PM