この記事は新歓ブログリレー72日目の記事です。
sea314です。
前回に引き続きprintfに関する記事を書きます。こちらの記事はかなり発展的な内容になるので、printfについて基礎的な知識がない人は前日の記事から読むことを推奨します。
printfファミリー
printf関数の他に、出力先、引数のとり方などが違うだけで使い方が同じ関数がいくつも存在します。中には一部環境では存在しない、関数名が異なる場合があります。
関数名 | 出力先 | 引数 | その他違い | 備考 |
---|---|---|---|---|
sprintf | 文字列 | 可変引数 | ||
snprintf | 文字列 | 可変引数 | 出力バイト数制限できる | |
fprintf | ファイル | 可変引数 | ||
vprintf | 標準出力 | va_list | ||
vsprintf | 文字列 | va_list | ||
vsnprintf | 文字列 | va_list | ||
vfprintf | ファイル | va_list | ||
_scprintf | 無し | 可変引数 | 出力せずにバイト数を返す | VisualStudio専用 |
_vscprintf | 無し | va_list | 出力せずにバイト数を返す | VisualStudio専用 |
asprintf | 文字列 | 可変引数 | asprintf内で文字列を確保する | GCC拡張機能 |
vasprintf | 文字列 | va_list | vasprintf内で文字列を確保する | GCC拡張機能 |
見ていれば気づくと思いますが、sは文字列出力、fはファイル出力、nは出力文字数制限あり、vはva_listになっています。
va_listとは?
可変引数を持った関数を自作する際に使う型です。可変引数の関数は各引数に名前を付けることが出来ないので、va_listという型で一括管理します。
以下のmyprintf関数はprintfの出力にmessage:という文字を先頭に付け加える関数です。printfの戻り値は出力バイト数なので、printf("message:")とvprintf(fmt, args)の値を足したものを戻り値にしています。
#include <stdio.h>
#include <stdarg.h>
int myprintf(const char* fmt, ...){
va_list args;
va_start(args, fmt);
int ret = printf("message:");
ret += vprintf(fmt, args);
va_end(args);
return ret;
}
int main(){
double a=3.141592;
myprintf("%.3f", a);
return 0;
}
出力
message:3.142
このように、printfのようなフォーマット機能をもつ関数を自作する際にはv付き関数群を使うことになります。
asprintf、vasprintfについて
この2つの関数は次のように宣言されています。
int asprintf(char **strp, const char *fmt, ...);
int vasprintf(char **strp, const char *fmt, va_list ap);
fmtを文字列に直し、必要なバイト数を確保して書き込んだあと、strpに返します。
#include <stdio.h>
#include <stdlib.h>
int main(){
char *str;
asprintf(&str, "%.3f", 3.14);
puts(str);
free(str);
return 0;
}
このように使います。
文字列に出力する際に配列サイズやバッファオーバーフローを気にしなくて良くなります。
_scprintf、_vscprintfについて
この2つの関数は次のように宣言されています。
int _scprintf(const char *fmt, ... );
int _vscprintf(const char *fmt, va_list ap);
fmtを文字列に直し、必要なバイト数を計算して返します。出力はしません。
この関数に限らず、printfファミリーの関数は戻り値が出力バイト数なので、出力しないこと以外変わった点はありません。しかし、printfの戻り値をわざわざ受け取ることは普通しないので使われ方は全く違います。
#include <stdio.h>
#include <stdlib.h>
int main(){
char *str = (char *)malloc(_scprintf("%.3", 3.14)+1);
sprintf(str, "%.3f", 3.14);
puts(str);
free(str);
return 0;
}
このように使います。
見た目asprintf、vasprintfよりも使い勝手が悪そうですが、必要バイト数を返してくれるおかげでsprintf後に更に文字操作する際にバッファを大きめに取れたりと融通が効きます。
asprintf、vasprintfを実装する
VisualStudioを使っている人はasprintf、vasprintfを使うことが出来ませんが、_vscprintfを使うことで実装できます。
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
int vasprintf(char** strp, const char* fmt, va_list ap) {
char* buf = (char*)malloc(_vscprintf(fmt, ap) + 1);
if (buf == NULL) {
if (strp != NULL) {
*strp = NULL;
}
return -1;
}
*strp = buf;
return vsprintf(buf, fmt, ap);
}
int asprintf(char** strp, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
int ret = vasprintf(strp, fmt, args);
va_end(args);
return ret;
}
既存関数を書式対応化する
Visual Studio独自関数の_vscprintf、_vscwprintf (_vscprintfのワイド文字版)、_vswprintf (vsprintfのワイド文字版)を使うので、他のコンパイラではエラーになる可能性が高いです。
MessageBox
いろんなライブラリにMessageBox関数はあると思いますが、WinAPIのMessageBox関数を書式対応化してみます。
MessageBoxは次のように宣言されています。
#ifdef UNICODE
#define MessageBox MessageBoxW
#else
#define MessageBox MessageBoxA
#endif // !UNICODE
int MessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
int MessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType);
MessageBoxのような文字列を引数にとる関数は歴史的経緯でUNICODEマクロが定義されているかどうかで実際に呼び出される関数がA版かW版か切り替わるようになっています。これに合わせてMyMessageBoxAとMyMessageBoxWの2つの関数を作ります。
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <windows.h>
#ifdef UNICODE
#define MyMessageBox MyMessageBoxW
#else
#define MyMessageBox MyMessageBoxA
#endif // !UNICODE
int MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType, ...) {
va_list arg;
va_start(arg, uType);
int b = 0;
if (lpText == NULL) {
b = MessageBoxA(hWnd, NULL, lpCaption, uType);
}
else {
LPSTR buf = (LPSTR)malloc(sizeof(char) * _vscprintf(lpText, arg) + 1);
vsprintf(buf, lpText, arg);
b = MessageBoxA(hWnd, buf, lpCaption, uType);
free(buf);
}
va_end(arg);
return b;
}
int MyMessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType, ...) {
va_list arg;
va_start(arg, uType);
int b = 0;
if (lpText == NULL) {
b = MessageBoxW(hWnd, NULL, lpCaption, uType);
}
else {
LPWSTR buf = (LPWSTR)malloc(sizeof(WCHAR) * _vscwprintf(lpText, arg) + 1);
_vswprintf(buf, lpText, arg);
b = MessageBoxW(hWnd, buf, lpCaption, uType);
free(buf);
}
va_end(arg);
return b;
}
例
MyMessageBoxA(NULL, "%012.3f\n%*.*f\n%12.3e\n", "", MB_OK, -12.3456, 12, 3, -12.3456, -12.3456);
表示
OutputDebugString
この関数はVisualStudioのデバッグウィンドウに文字を出力する関数です。
#ifdef UNICODE
#define OutputDebugString OutputDebugStrinW
#else
#define OutputDebugString OutputDebugStringA
#endif // !UNICODE
void OutputDebugStringA(LPCSTR lpOutputString);
void OutputDebugStringW(LPCWSTR lpOutputString);
この関数も書式対応させます。
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <windows.h>
#ifdef UNICODE
#define MyOutputDebugString MyOutputDebugStringW
#else
#define MyOutputDebugString MyOutputDebugStringA
#endif // !UNICODE
void MyOutputDebugStringA(LPCSTR lpOutputString, ...) {
va_list arg;
va_start(arg, lpOutputString);
if (lpOutputString == NULL) {
MyOutputDebugStringA(lpOutputString);
}
else {
LPSTR buf = (LPSTR)malloc(sizeof(char) * _vscprintf(lpOutputString, arg) + 1);
vsprintf(buf, lpOutputString, arg);
OutputDebugStringA(buf);
free(buf);
}
va_end(arg);
}
void MyOutputDebugStringW(LPCWSTR lpOutputString, ...) {
va_list arg;
va_start(arg, lpOutputString);
if (lpOutputString == NULL) {
MyOutputDebugStringW(lpOutputString);
}
else {
LPWSTR buf = (LPWSTR)malloc(sizeof(WCHAR) * _vscwprintf(lpOutputString, arg) + 1);
_vswprintf(buf, lpOutputString, arg);
OutputDebugStringW(buf);
free(buf);
}
va_end(arg);
}
例
MyOutputDebugString(TEXT("%+.*d\n"), 7, 12345);
MyOutputDebugString(TEXT("%7d\n"), -12345);
MyOutputDebugStringA("%07d\n", -12345);
MyOutputDebugStringW(L"%#x\n", 12345);
表示
その他
Windowsプログラミングでprintfを使う
通常、Windowsプログラミングではprintf関数は使えませんが、簡易デバッグ用にprintfが使いたいことがあると思います。
プログラム開始時に以下を呼び出すことでコンソール画面を作成し、printfをそのコンソールに出力させることが出来ます。
AllocConsole();
freopen("CONOUT$", "w", stdout);
コンソールを使い終わったら以下を呼び出してコンソールを破棄します。
FreeConsole();
このような感じになります。
最後に
今日の記事ではprintfを使った応用について書きましたが、一部の環境でしか動かないと思うので気をつけて下さい。
明日はmazreanさんの記事です。お楽しみに!