C言語アドベントカレンダー の 12/20 が空いていたので、ちょっと書いてみた。
printf
の書式文字列について、私が知らなかったことをメモする。
動作確認は、macOS Mojave 上の clang と gcc-8 で行った。
桁区切り文字を出力するフラグ文字
具体的にはこんな感じ:
#include <stdio.h>
#include <locale.h>
int main(void)
{
setlocale(LC_ALL, "sk_SK.ISO8859-2" ); // Slovak - SLOVAKIA
printf("%'d\n", 12345678); //=> 12 345 678
printf("%'f\n", 12345678.12345678); //=> 12 345 678,123457
return 0;
}
ロケールによって出たり出なかったりする桁区切り文字。
スロバキアでは空白区切りらしい。
桁区切り文字とは関係ないけど、小数点がコンマになっているところにも注目。
なお。
デフォルトロケールだとシングルクオートなしと同じ結果になるので面白くない。
intmax_t 用の長さ修飾子
C99 には intmax_t
, uintmax_t
という、long long
よりも長いかもしれない整数型がある。
というわけで、それ用の長さ修飾子がある。
こんな感じ。
#include <stdio.h>
#include <stdint.h>
int main(void)
{
uintmax_t u = UINTMAX_MAX;
intmax_t i = INTMAX_MAX;
printf("%ju\n", u ); // => 18446744073709551615
printf("%jd\n", i ); // => 9223372036854775807
return 0;
}
でも、手元の処理系では intmax_t
, uintmax_t
ともに 64bit なのであんまり面白くない。
char であることを示す長さ修飾子
char はどうせ int に昇格するので、コンパイラに警告を出させる以外にはあまり意味がない。
こんな感じ。
#include <stdio.h>
int main(void)
{
signed char sc = -128;
unsigned char uc = 255;
printf( "%hhd %d %hhu %u\n", sc, sc, uc, uc ); //=>-128 -128 255 255
printf( "%hhd %d %hhu %u\n", -400, -400, 400, 400 ); //=> 112 -400 144 400
return 0;
}
clang の場合、あとの方の printf は
warning: format specifies type 'char' but the argument has type 'int' [-Wformat]
warning: format specifies type 'unsigned char' but the argument has type 'int' [-Wformat]
という警告が出る。
gcc-8 は警告出してくれない。
※ ここから、2022年1月加筆
……と書いておいたら、ありがたいコメント が。
なるほど。
私としては負の値を 16 進数で出すことは稀なので気づきませんでした。
と思った上で man を見てみると
printf( "foo %hhn", &x );
とすると、&x
を char* だと思ってくれるという機能もあるらしい。不要。
※ ここまで、2022年1月加筆
ptrdiff_t 用の長さ修飾子
size_t
用の z
は知っていたけど、ptrdiff_t
用の t
は知らなかった。
#include <stdio.h>
int a[1ULL<<44]; // 意外と行ける
int main(void)
{
printf( "%td\n", a+sizeof(a)/sizeof(*a) - a ); // => 17592186044416
return 0;
}
16進数の浮動小数点変換指定子
誤差なく浮動小数点を出力したい場合に必要になる、16進数浮動小数点。
#include <stdio.h>
int main(void)
{
printf( "%a %f\n", 0.1, 0.1 ); //=> 0x1.999999999999ap-4 0.100000
return 0;
}
とはいえ、出てきた値を読むのが面倒なのであまり使いみちがない。
脆弱性の原因となる %n
昔からあるらしいけど、知らなかった。
C11 から入る printf_s
には含まれないらしい。
#include <stdio.h>
int main(void)
{
int n0, n1, n2;
printf( "foo %n bar %n baz\n%n", &n0, &n1, &n2 ); //=> foo bar baz
printf( "%d %d %d\n", n0, n1, n2 ); //=> 4 9 14
return 0;
}
引数に int
へのポインタを置いておくと、%n
までに出力した文字の数が入るらしい。
恐ろしい。
余計なことすんな、という感じ。
IPA にも フォーマット文字列攻撃対策 という記事が上がっている。こわいこわい。
というわけで
というわけで、printf
のマイナーな機能の紹介はこれでおしまい。
いずれも C11/C99 準拠だと思う。
あとそういえば。
関数へのポインタを (void*)
に安全にキャストすることはできないことになっているけど、関数へのポインタ用の 変換指定子 ってないんだね。必要だと思うんだけどなぁ。