始めに
本記事の目的は、Windows上で標準ののprintf
機能をできるだけ原始的に再現することです。具体的には、Windowsコンソールアプリケーションにおいて、printf
に近い動作を自作関数で実装し、内部ではバッファを活用する方法を紹介します。
尚、本実装では日本語の出力には対応していません。これは、コンソール上での日本語処理には文字コードやバッファ管理の複雑さが伴い、実装の難易度が大幅に上がるためです。
バッファの操作やprintf
のフォーマット指定子の詳しい説明については、以下に挙げた以前の記事をご参照下さい。
前に投稿した記事
Windows APIを用いてprintf関数を再現
Windows コンソールバッファに直接書き込み(黒い画面に描画する原理)
コード
main.c
main.c
// 動作確認用
#include "mystdio.h"
#include "console_buffer.h"
#include <stdio.h>
int main() {
init_console_buffer();
int printfRtn = my_printf("num=%d str=%s num2=%d str2=%s hex=%x hex2=%x\n",3,"aiueo",678,"kakikukeko",17,15);
my_printf("myPrintfRtn = %d\n",printfRtn);
return 0;
}
console_buffer.h
console_buffer.h
#ifndef CONSOLE_BUFFER_H
#define CONSOLE_BUFFER_H
#include <windows.h>
void init_console_buffer();
int console_put_char(char c);
#endif
console_buffer.c
console_buffer.c
#include "console_buffer.h"
// 標準出力ハンドル(コンソール)を格納する変数
static HANDLE hConsole;
// カーソルの現在位置を管理する変数
static COORD cursor;
// コンソールのカーソル位置を初期化
void init_console_buffer() {
hConsole = GetStdHandle(STD_OUTPUT_HANDLE); // 標準出力ハンドルを取得
CONSOLE_SCREEN_BUFFER_INFO csbi; // コンソールの画面バッファ情報を格納するための構造体
GetConsoleScreenBufferInfo(hConsole, &csbi); // 現在のバッファ情報を取得
cursor = csbi.dwCursorPosition; // 現在のカーソル位置を保存
}
// 一文字をコンソールに出力する関数
int console_put_char(char c) {
DWORD written;
// 改行文字の場合、Xを0にリセットしてYを+1(次の行へ)
if (c == '\n') {
cursor.X = 0;
cursor.Y += 1;//次の行に移動
SetConsoleCursorPosition(hConsole, cursor); // カーソル位置を更新
return 1;
}
// 通常の文字を現在のカーソル位置に出力
if (WriteConsoleOutputCharacterA(hConsole, &c, 1, cursor, &written)) {
cursor.X += 1; // 出力後にX座標を+1
SetConsoleCursorPosition(hConsole, cursor); // カーソル位置を更新
return (int)written; // 書き込んだ文字数(成功時: 1)
} else {
return 0; // 失敗時
}
}
mystdio.h
mystdio.h
#ifndef MYSTDIO_H
#define MYSTDIO_H
#include <stdarg.h>
int my_printf(const char *format, ...);
#endif // MYSTDIO_H
mystdio.c
mystdio.c
#include "mystdio.h"
#include "console_buffer.h"
#include <windows.h>
#include <string.h>
#include <stdarg.h>
// 一文字出力
int write_char(char c) {
return console_put_char(c);
}
// 文字列を出力
int write_str(const char *str) {
int count = 0;
while (*str) {
count += write_char(*str++);
}
return count;
}
// 整数を出力
int write_int(int value) {
char buffer[12];
int i = 0;
int count = 0;
if (value < 0) {
count += write_char('-');
value = -value;
}
if (value == 0) {
return count + write_char('0');
}
while (value > 0) {
buffer[i++] = '0' + (value % 10);
value /= 10;
}
while (i--) {
count += write_char(buffer[i]);
}
return count;
}
// 10進数を16進数へ変換して出力
int write_hex(unsigned int value) {
char buffer[9];
int i = 0;
int count = 0;
if (value == 0) {
return write_char('0');
}
while (value > 0) {
int digit = value % 16;
if (digit < 10)
buffer[i++] = '0' + digit;
else
buffer[i++] = 'a' + (digit - 10);
value /= 16;
}
while (i--) {
count += write_char(buffer[i]);
}
return count;
}
// printf
int my_printf(const char *format, ...) {
va_list args;
va_start(args, format);
int count = 0;
while (*format) {
if (*format == '%' && *(format + 1)) {
format++; //%の次の文字を指す
if (*format == '%') {
count += write_char('%'); // %%が連続した場合は1つ出力
} else if (*format == 's') {
const char *str = va_arg(args, const char*);
count += write_str(str);
} else if (*format == 'd') {
int num = va_arg(args, int);
count += write_int(num);
} else if (*format == 'x') {
unsigned int num = va_arg(args, unsigned int);
count += write_hex(num);
} else {
count += write_char('%');
count += write_char(*format);
}
format++;
} else { //%〇以外の文字はそのまま1文字ずつ出力する
count += write_char(*format);
format++;
}
}
va_end(args);
return count;
}
動作結果
C:\Users\nanashi\test>gcc main.c mystdio.c console_buffer.c -o myprintf.exe
C:\Users\nanashi\test>myprintf
num=3 str=aiueo num2=678 str2=kakikukeko hex=11 hex2=f
myPrintfRtn = 55
動作環境
Windows10 Version 10.0.19045.5737
gcc version 14.2.0