0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Windows APIを用いてprintf関数を再現

Last updated at Posted at 2025-04-12

実装する機能

  • %d %s %xによる整数、文字列、16進数の出力
  • 返り値は標準関数に準拠し書き込んだ文字数
  • %10d等の有効数字は一旦実装しない

実際のコード

main.c
// 動作確認用
#include "mystdio.h"
#include <stdio.h>

int main() {
	int printfRtn = printf("num=%d str=%s num2=%d str2=%s hex=%x hex2=%x\n",3,"aiueo",678,"kakikukeko",17,15);
    int myPrintfRtn = my_printf("num=%d str=%s num2=%d str2=%s hex=%x hex2=%x\n",3,"aiueo",678,"kakikukeko",17,15);
    int printfRtn2 = printf("my_printf:英語以外も確認。あいうえお\n");
	int myPrintfRtn2 = my_printf("my_printf:英語以外も確認。あいうえお\n");
	int printfRtn3 = printf("%%\n");
	int myPrintfRtn3 = my_printf("%%\n");
	
	my_printf("返り値が標準printfと一致するか\n");
	my_printf("printfRtn = %d\n",printfRtn);
	my_printf("printfRtn2 = %d\n",printfRtn2);
	my_printf("printfRtn3 = %d\n",printfRtn3);

	my_printf("myPrintfRtn = %d\n",myPrintfRtn);
	my_printf("myPrintfRtn2 = %d\n",myPrintfRtn2);
	my_printf("myPrintfRtn3 = %d\n",myPrintfRtn3);
	
	my_printf("\n");
	return 0;
}
mystdio.h
#ifndef MYSTDIO_H
#define MYSTDIO_H

#include <stdarg.h>
int my_printf(const char *format, ...);
#endif // MYSTDIO_H
mystdio.c
#include "mystdio.h"
#include <windows.h>
#include <string.h>
#include <stdarg.h>

// 一文字出力
int write_char(char c) {
    DWORD written = 0;
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    if (WriteConsoleA(hConsole, &c, 1, &written, NULL)) {
        return (int)written;  // 何文字書き込みに成功したか(WriteConsoleAからの返り値)
    } else {
        return 0;  // 書き込み失敗
    }
}

// 文字列を出力
int write_str(const char *str) {
    DWORD written = 0;
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    int len = (int)strlen(str);
    if (WriteConsoleA(hConsole, str, len, &written, NULL)) {
        return (int)written;  // 何文字書き込みに成功したか
    } else {
        return 0;  // 書き込み失敗
    }
}

// 整数を出力
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;
}

実行結果

網羅性はかなり低いですが仕事ではないのでご容赦下さい。

コンパイル及び実行.bash
C:\Users\nanasi\myprintf>gcc mystdio.c main.c -o main.exe -Wall

C:\Users\nanasi\myprintf>m
num=3 str=aiueo num2=678 str2=kakikukeko hex=11 hex2=f
num=3 str=aiueo num2=678 str2=kakikukeko hex=11 hex2=f
my_printf:英語以外も確認。あいうえお
my_printf:英語以外も確認。あいうえお
%
%
返り値が標準printfと一致するか
printfRtn = 55
printfRtn2 = 37
printfRtn3 = 2
myPrintfRtn = 55
myPrintfRtn2 = 37
myPrintfRtn3 = 2

各関数の説明

write_char、write_strはWriteConsoleAのAPIを呼び出しているだけなので割愛します。

write_int

    if (value < 0) {
        count += write_char('-');
        value = -value;
    }

count += write_char('-');
負の数であった場合は初めに-の符号を出力します。
WriteConsoleAを呼び出すとコードが長くなるので上で作ったwrite_char関数を利用しています。

value = -value;
既に-の符号を出力したので負負で正の数に変換し、これ以降は符号を考慮せずに処理します。

    if (value == 0) {
        return count + write_char('0');
    }

0の場合は下の処理に入ってエラーを吐いたら嫌なのでそのまま出力します。

    while (value > 0) {
        buffer[i++] = '0' + (value % 10);
        value /= 10;
    }

ここがこの関数の要です。
例えば123を文字として出力する場合に、value(123) % 10=3(余り)となり最後の1文字を取り出せます。
'0' + (value % 10)この部分の意味ですが、ASCIIで'0'は48、'3'は51、
つまり数字に48を足せばその数字のASCIIコードが算出できます。
(多少ややこしいですが、画面へはASCIIコードで出力するので変換しなくてはなりません。)
48とべた書きすると意味が分かりにくいので'0'と書いていますがchar型は数値としても計算できるため問題ありません。
value(123) / 10= 12(商)となり、3より前の数字を取り出せます。
valueが0になるまで上記の処理を繰り返します。

    while (i--) {
        count += write_char(buffer[i]);
    }

上記の処理が完了するとbufferには逆向きに数字が格納されています。
例えば引数value=123の場合
buffer[0]='3'
buffer[1]='2'
buffer[2]='1'
iにはwhile (value > 0)処理が終わった段階で文字数-1(例だと2)が格納されているので、減算しながら逆向きに出力していきます。

write_hex

10進数を16進数へ変換して表示します。

    while (value > 0) {
        int digit = value % 16;
        if (digit < 10)
            buffer[i++] = '0' + digit;
        else
            buffer[i++] = 'a' + (digit - 10);
        value /= 16;
    }

この部分ですが10進数のwrite_int関数の処理と考え方は同じです。
value=305441741の場合、16進数は1234abcd
305441741%16₌13(=16進数d)となり1の位の数字を取り出せます。

0~9は10進数と16進数に違いはありませんが10~15の場合はa~fへ変換しないといけません。(10:a 11:b 12:c 13:d 14:e 15:f)

buffer[i++] = 'a' + (digit - 10)
例えば処理中の位が13の場合 'a'+(13 - 10) = 'd'となります。
'a'=97' 'd'=100 (ACSIIコード表をご参照下さい。)

    while (i--) {
        count += write_char(buffer[i]);
    }

この部分もwrite_int関数の処理と考え方は同じです。
value=305441741の場合、
buffer[0]='d'
buffer[1]='c'
buffer[2]='b'
buffer[3]='a'
buffer[4]='4'
buffer[5]='3'
buffer[6]='2'
buffer[7]='1'
の順で入っているので逆順に表示していきます。

my_printf

この関数の処理に関しては%の次の英字に合わせて上で定義した各関数を呼び出しているだけなのででコメント部分の説明だけで十分かと思いますので割愛します。

一つ可変数個の引数に関して説明しておきます。
int my_printf(const char *format, ...) {
のように引数の所で...と定義します。

va_list args;
va_start(args, format);

呼び出すところで入れた順番に型名 変数名 = va_arg(args, const 型名)を順番に実行していくと取得できます。

最後にva_end(args);を実行しないといけないようです。

この中身も理解しようとしましたがWindowsでは深いところまで掘り下げることが難しいので後日Linuxで研究してみようと思います。

以上

追記

コメントにてWriteConsoleAを使用した上の書き方ではリダイレクトした場合に失敗するとご指摘がありましたので試してみました。

C:\Users\nanasi>cd myprintf
C:\Users\nanasi\myprintf>main.exe > output.txt
output.txt
num=3 str=aiueo num2=678 str2=kakikukeko hex=11 hex2=f
my_printf:英語以外も確認。あいうえお
%

確かにmy_printf()の分が一切出力されておりません。
そこで標準出力ハンドルについて調べました。

標準出力ハンドルの性質​​
GetStdHandle(STD_OUTPUT_HANDLE) で取得するハンドルは、
​​リダイレクトされていない場合​​ → コンソールを指す
​​リダイレクトされている場合​​ → ファイルやパイプを指す
​​WriteFileの挙動​​
ハンドルがコンソールを指す場合 → コンソールに文字を出力
ハンドルがファイルを指す場合 → ファイルに書き込み

WriteConsoleA→WriteFileへ変更
幸い引数は同じため、他の部分の変更は一切無し

int write_char(char c) {
    DWORD written = 0;
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    if (WriteFile(hConsole, &c, 1, &written, NULL)) {
        return (int)written;
    } else {
        return 0;
    }
}

int write_str(const char *str) {
    DWORD written = 0;
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    int len = (int)strlen(str);
    if (WriteFile(hConsole, str, len, &written, NULL)) {
        return (int)written;
    } else {
        return 0;
    }
}
C:\Users\nanasi\myprintf>main.exe > output.txt
C:\Users\nanasi\myprintf>
output.txt
num=3 str=aiueo num2=678 str2=kakikukeko hex=11 hex2=f
my_printf:英語以外も確認。あいうえお
%
返り値が標準printfと一致するか
printfRtn = 55
printfRtn2 = 37
printfRtn3 = 2
myPrintfRtn = 55
myPrintfRtn2 = 37
myPrintfRtn3 = 2

num=3 str=aiueo num2=678 str2=kakikukeko hex=11 hex2=f
my_printf:英語以外も確認。あいうえお
%

無事に出力されました!

0
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?