std::printf 系関数の length modifier
はじめに
C++ の std::printf
系関数の書式指定文字列の length modifier のメモです。
std::printf
系の関数は知名度は高いと思いますが、書式指定を誤っているケースをちらほら見かけます。
この記事では、書式指定文字列中の length modifier のうち、さらに一部に特化して言及します。
なお、書式指定文字はすべて覚える必要はなく、その都度 cppreference.com[2] などを確認すればよいと思います。 テストではないので。
length modifier とは
length modifier[3] とは引数のサイズを指定する文字のことです。
まずは例を示します。
std::printf
関数で long
(signed long int
) 型の値を 10 進数表現で出力するには次のようにしますね:
signed long int value{/* 適当な値 */};
std::printf("%ld", value);
第 1 引数の書式指定文字列 "%ld"
のうち、 l
が length modifier です (ちなみに d
は conversion specifier です)。
この length modifier l
は、続く第 2 引数 value
の型のサイズが long
であることを std::printf
関数に伝えます。
length modifier として用意されているのは C++23 時点では全部で次の 9 種類です: hh, h, (なし), l, ll, j, z, t, L
これらの文字の意味は後ろに続く conversion specifier によって変わります。
conversion specifier は先の Code 1 の例における "%ld"
のうちの d
です。
たとえば、符号つき整数に適用して 10 進数表現で書式化する conversion specifier d の場合、 length modifier を指定しない (つまり "%d"
) と引数は int
と解釈されますが、 length modifier ll (小文字のエル 2 つ) をつける (つまり "%lld%"
) と long long int
と解釈されるようになります (Table 1)。
Table 1. length modifier と conversion specifier d への適用例
length modifier | 書式指定文字列 | 期待される引数の型 |
---|---|---|
hh | "%hhd" |
signed char |
h | "%hd" |
short (signed short int ) |
(なし) | "%d" |
int (signed int ) |
l | "%ld" |
long (signed long int ) |
ll | "%lld" |
long long (signed long long int ) |
プロモーション
前の節でさまざまな length modifier をご紹介しましたが、「hh や h なんて使ったことないよ, 使わなくても困ったことないよ」という方もいらっしゃると思います。
そのあたりの事情を簡単にご説明します。
突然ですが、 C++ にはプロモーション (promotions; 昇格) という仕様があります。
プロモーションには整数のプロモーション (integral promotions)[4] と浮動小数点数のプロモーション (floating-point promotion)[5] があります。
簡単に説明すると、前者の代表的なものは int
型より小さい整数型の値は int
型の値に変換されるというものです。
後者は float
型の値は double
型の値に変換されるものです。
簡単のため、以降は前者; 整数のプロモーションに限定して話を進めます。
このプロモーションによって、 signed char
型や short
型の値を std::printf
関数に渡した場合、それらはいずれも int
型に変換されてから渡されます。
length modifier hh や h は、 int
型に変換された値を変換前の型に戻してから書式化することを指示します。
length modifier hh の意味の説明[6]
Specifies that a following b, d, i, o, u, x, or X conversion specifier applies to a signed char or unsigned char argument (the argument will have been promoted according to the integer promotions, but its value shall be converted to signed char or unsigned char before printing); (略)
ただし、 signed char
型や short
型の値は int
型で表現できますので、それらを "%d"
で (int
型のまま) 書式化しても特に問題となることはないと思います。
std::size_t 型
std::size_t
は、 <cstddef> などのヘッダで定義される型です。
規格では次のように規定されています[7]:
The type size_t is an implementation-defined unsigned integer type that is large enough to contain the size in bytes of any object.
std::size_t
型は sizeof
演算子の結果の型であるほか、標準ライブラリの各種コンテナの size
メンバ関数が返す型でもあり、コード中での出番は多いです。
同型のサイズは先の規定のように主に (ターゲットの) CPU アーキテクチャに合わせて決められていて、 4 Bytes の環境もあれば 8 Bytes の環境もあり、まちまちです。
一方で int
などの基本型 (組み込み型) のサイズはそれとは別の事情によって決められているため、 std::size_t
型の値を "%u"
(unsigned int
用) や "%lu"
(unsigned long
用) などで出力すると移植性が低下してしまいます。
たとえば、 64 bit の環境 (x86-64 や AArch64 など) では std::size_t
型のサイズは一般には 8 Bytes です。
それに対して基本型 (組み込み型) はというと、たとえば long
(long int
) のサイズは Linux では 8 Bytes ですが Windows (Win64) では 4 Bytes と異なります[8]。
std::size_t
型の値を 10 進数で出力する場合、 Linux では "%lu"
(unsigned long
用) でもかまいませんが、 Windows では "%lu"
では足りず "%llu"
(unsigned long long
用) とする必要があります。
こういったことを考慮すると、ポータブルなコードを書くのは大変なことのように思えますね。
そこで、 std::size_t
型には専用の length modifier z が用意されています。
(同型は符号なしですから) 10 進数表現で文字列化する場合には "%zu"
とすればよいです。
固定長整数型
C++ ではほとんどの基本型 (組み込み型) のサイズは規定されていませんので、サイズを明示的に指定したい場合には、 std::int32_t
をはじめとした <cstdint> で定義されている固定長整数型 (fixed width integer types)[10] を使うこともあるでしょう。
たとえば、 std::int32_t
型は、ある環境では int
かもしれませんし、別のある環境では long
かもしれません。
この型の値を std::printf
関数で文字列化する場合、 int
型なら書式指定は "%d"
ですが、 long
型であれば "%ld"
とする必要があります。
移植性を確保したまま固定長整数型を std::printf
系関数で文字列化するにはどうしたらよいのでしょうか。
実は、そのためのマクロが <cinttypes> に用意されています。
たとえば std::int32_t
型であれば PRId32
です。
このマクロは length modifier と conversion specifier を表す文字列に展開されるので、これの前に "%"
(と必要に応じて他の指定子) を書いてあげます。
他の型用のマクロは参考文献 4 をご覧ください。
参考文献
- std::printf, std::fprintf, std::sprintf, std::snprintf - cppreference.com.
https://en.cppreference.com/w/cpp/io/c/fprintf - N3054: Programming languages ⸺ C, working draft.
- N4950: Working Draft, Standard for Programming Language C++.
- Fixed width integer types (since C++11) - cppreference.com.
https://en.cppreference.com/w/cpp/types/integer - 64ビット - Wikipedia.
https://ja.wikipedia.org/wiki/64ビット
-
組み込みシステムでは標準出力がない (あるいは使わない) こともめずらしくありませんが、
std::sprintf
関数経由でログに出力する場合などに用いられていると思います。 ↩︎ -
参考文献 1 ↩︎
-
参考文献 2 の 7.23.6.1 ↩︎
-
参考文献 3 の 7.3.7 [conv.prom] ↩︎
-
参考文献 3 の 7.3.8 [conv.fpprom] ↩︎
-
参考文献 2 の 7.23.6.1 ↩︎
-
参考文献 3 の 17.2.4 [support.types.layout] ↩︎
-
参考文献 5 ↩︎
-
正しくはサフィックス u または U と併用すると
std::size_t
型と解釈され、 z または Z 単独では符号つきのstd::size_t
型となります。 ↩︎ -
参考文献 4 ↩︎
Discussion