int
: %hhd
、%hd
、%ld
および%lld
mean signed char
、short
、に適用できる (少なくとも C99 では) 長さ修飾子がlong
ありlong long
ます。double
: %Lf
meansに適用可能な長さ修飾子さえありlong double
ます。
問題は、なぜ省略したのfloat
かということです。パターンに従って、それはだったかもしれません%hf
。
int
: %hhd
、%hd
、%ld
および%lld
mean signed char
、short
、に適用できる (少なくとも C99 では) 長さ修飾子がlong
ありlong long
ます。double
: %Lf
meansに適用可能な長さ修飾子さえありlong double
ます。
問題は、なぜ省略したのfloat
かということです。パターンに従って、それはだったかもしれません%hf
。
C の可変個引数関数呼び出しでは、任意のfloat
引数が a に昇格 (つまり、変換) されるdouble
ため、printf
aを取得し、その実装内でそれを取得するためにdouble
使用します。va_arg(arglist, double)
過去 (C89 および K&R C) では、すべてのfloat
引数がdouble
. 現在の標準では、明示的なプロトタイプを持つ固定アリティ関数のこの昇格を省略しています。これは、実装のABIおよび呼び出し規則に関連しています (詳細は説明されています) 。実際には、float
値は引数として渡されたときに倍精度浮動小数点レジスタにロードされることがよくありますが、詳細はさまざまです。例としてLinux x86-64 ABI 仕様を読んでください。
また、特定のフォーマット制御文字列を指定する実際的な理由はありません。出力の幅を (たとえば で)必要に応じてfloat
調整できるためです。%8.5f
%hd
scanf
printf
それ以上に、理由( -promoted to in caller-in の%hf
指定を省略する)は歴史的なものだと思います: 最初は、C はシステム プログラミング言語であり、HPC 言語ではありませんでした (Fortran はおそらく 1990 年代後半まで HPC で好まれていました)。あまり重要ではありませんでした。それは、メモリ消費量を減らす方法のように考えられていました(そして今でもそうです) 。また、今日の FPU は (デスクトップまたはサーバー コンピューター上で) 十分に高速であるため、メモリ使用量を減らす手段としての except の使用を避けることができます。基本的に、 everyはどこかに (おそらくFPU または CPU の内部) に変換されていると考えるべきです。float
double
printf
float
short
float
float
double
実際には、あなたの質問は次のように言い換えることができます: なぜ%hd
存在するのかprintf
(基本的に役に立たない場所。渡したときにprintf
が取得されますが、それが必要です!)。理由はわかりませんが、システムプログラミングよりも便利かもしれないと思います。int
short
scanf
倍精度値が -s の範囲外である場合の未定義の動作で、 for に受け入れられるように次の ISO C 標準をロビー活動することに時間を費やすことができます ( at 呼び出しに昇格され、 -s が に昇格されます) 、for%hf
によって対称的に受け入れられます。ポインター。頑張ってください。printf
float
double
printf
short
int
float
%hf
scanf
float
デフォルトの引数の昇格のため。
printf()
は可変引数関数 (...
そのシグネチャ内) であり、すべてのfloat
引数は に昇格されdouble
ます。
C11 §6.5.2.2 関数呼び出し
6 呼び出された関数を示す式がプロトタイプを含まない型を持つ場合、各引数に対して整数昇格が実行され、型を持つ引数
float
は に昇格されdouble
ます。これらは、デフォルト引数プロモーションと呼ばれます。7 関数プロトタイプ宣言子の省略記号表記により、最後に宣言されたパラメーターの後で引数の型変換が停止します。デフォルトの引数昇格は、末尾の引数に対して実行されます。
可変引数関数を呼び出すときのデフォルトの引数昇格により、値は関数呼び出しの前にfloat
暗黙的に に変換され、double
値をに渡す方法はありませんfloat
printf
。float
に値を渡す方法がないため、値printf
の明示的な書式指定子は必要ありませんfloat
。
そうは言っても、AntoineLはコメントで興味深い点を提起しました%lf
(現在scanf
は引数 type に対応するために使用されていますdouble *
) はlong float
、C89 以前の時代には型シノニムであった " "をかつて意味していた可能性があります。 C99の根拠。その論理により、が に変換された値を%f
表すことを意図していたのは理にかなっているかもしれません。float
double
hh
長さ修飾子とh
長さ修飾子に関して、%hhu
これらの書式指定子の明確に定義された使用例を示します。たとえば、次のように、ラージまたはキャストなし%hu
で最下位バイトを出力できます。unsigned int
unsigned short
printf("%hhu\n", UINT_MAX); // This will print (unsigned char) UINT_MAX
printf("%hu\n", UINT_MAX); // This will print (unsigned short) UINT_MAX
int
からchar
またはへの縮小変換のshort
結果は特に明確に定義されていませんが、少なくとも実装定義です。つまり、この決定を実際に文書化するには実装が必要です。
パターンに従って、それはそうあるべきでした
%hf
。
観察したパターンに従って、%hf
の範囲外の値を に変換する必要がありfloat
ますfloat
。しかし、 から へのそのような狭義の変換は未定義の動作double
をもたらし、.などはありません。あなたが見るパターンは意味がありません。float
unsigned float
正式には、 は引数%lf
を意味しません。引数long double
を渡すと、未定義の動作が呼び出されます。ドキュメントから次のことが明示されています。long double
l
(エル) ... に続く、、、、、、、、a
または変換指定子にA
はe
影響E
しません。f
F
g
G
他の誰もこれに気付いていないことに驚いていますか?%lf
はdouble
と同じように引数を示します%f
。を出力したい場合は、 (大文字のエル)long double
を使用します。%Lf
%lf
for bothprintf
と andscanf
に対応するdouble
anddouble *
引数...%f
が例外的であるのは、先に述べた理由により、デフォルトの引数の昇格のみが理由であることが、今後は理にかなっているはずです。
...%Ld
という意味でもありませんlong
。つまり、未定義の動作です。
ISO C11 標準6.5.2.2 Function calls /6
およびから/7
、式のコンテキストでの関数呼び出しについて説明しています (私の強調):
6/ 呼び出された関数を示す式の型がプロトタイプを含まない場合、各引数に対して整数昇格が実行され、float 型の引数は double に昇格されます。これらは、デフォルト引数プロモーションと呼ばれます。
7/ 呼び出された関数を示す式がプロトタイプを含む型を持っている場合、引数は、あたかも代入によるかのように、対応するパラメーターの型に暗黙的に変換され、各パラメーターの型が非修飾バージョンになります。その宣言された型。関数プロトタイプ宣言子の省略記号表記により、最後に宣言されたパラメーターの後で引数の型変換が停止します。デフォルトの引数昇格は、末尾の引数に対して実行されます。
これは、プロトタイプの のfloat
後の引数が に変換され、呼び出しのファミリーがそのように定義されることを意味します ( et seq):...
double
printf
7.21.6.11
int fprintf(FILE * restrict stream, const char * restrict format, ...);
したがって、printf()
-family 呼び出しが実際にfloat を受け取る方法がないため、特別な書式指定子 (または修飾子) を使用する意味はほとんどありません。
%hhd
、%hd
、%ld
およびは、既定の引数の昇格のために冗長ですが、フォーマット文字列を との一貫性を高めるために%lld
に追加されました。printf
scanf
printf
では、なぜ%hf
追加されなかったのfloat
ですか? それは簡単です:scanf
の動作を見ると、float
既に書式指定子があります。です%f
。のフォーマット指定子double
は%lf
.
それ%lf
はまさに C99 が に追加したものprintf
です。C99 より前では、 の動作は%lf
定義されていませんでした (標準の定義が省略されているため)。C99 の時点では、 のシノニムです%f
。
float、double、または long double の個別の書式指定子があることを考えるとscanf
、理由がわかりませんしprintf
、同様の関数が同様の方法で実装されていませんでしたが、それが C / C++ と標準が最終的に終わった方法です。
プロセッサと現在のモードによっては、プッシュまたはポップ操作の最小サイズに問題が発生する可能性がありますが、これは、ローカル変数または構造内の変数のデフォルトの配置と同様に、デフォルトのパディングで処理できた可能性があります。Microsoft は、80 ビット (10 バイト) のサポートをlong double
16 ビットから 32 / 64 ビット コンパイラに移行したときに廃止し、現在はs (64 ビット / 8 バイト)long double
と同じように扱っています。double
必要に応じて 12 または 16 バイトの境界までパディングすることもできましたが、これは行われませんでした。
fscanf の下にある C の理論的根拠を読むと、次のことがわかります。
C99 の新機能: hh および ll の長さ修飾子が C99 で追加されました。ll は、新しい long long int 型をサポートします。hh は、文字型を他のすべての整数型と同じように扱う機能を追加します。これは、SCNd8 などのマクロを実装するのに役立ちます (7.18 を参照)。
したがってhh
、すべての新しい型をサポートする目的で が追加されたと考えられstdint.h
ます。これは、小さな整数には長さ修飾子が追加されたが、小さな浮動小数点数には追加されなかった理由を説明できます。
C90 が矛盾して持っていた理由を説明していませんが、そうではありませh
んhh
。C90 で指定されている言語は常に一貫しているわけではなく、単純です。そして、それ以降のバージョンは矛盾を継承しています。
C が発明されたとき、すべての浮動小数点値はdouble
、計算で使用される前、または関数 (を含む) に渡される前に、共通の型 (つまり ) に変換されていたため、浮動小数点型を区別するprintf
必要はありませんでした。printf
double
演算の効率と精度を向上させるために、IEEE-754 浮動小数点標準は、通常の 64 ビットよりも大きいが高速に処理できる80 ビット型を定義しました。a=b+c+d;
のような式が与えられた場合、合計を計算するよりも、すべてを 80 ビット型に変換し、3 つの 80 ビット数値を加算し、結果を 64 ビット型に変換する方が高速かつ正確になるという意図がありました。(b+c)
を 64 ビット型として追加し、それを に追加しd
ます。
新しい型をサポートするために、ANSI C はlong double
実装が新しい 80 ビット型または 64 ビットを参照できる新しい型を定義しましたdouble
。残念ながら、IEEE-754 の 80 ビット型の目的は、すべての値が昇格された方法で新しい型に自動的に昇格することでしたがdouble
、ANSI は、新しい型が に渡されるように、printf
または他の可変引数メソッドとは異なる方法でそれを作成しました。他の浮動小数点型であるため、このような自動昇格は受け入れられません。
したがって、C が作成されたときに存在した両方の浮動小数点型は同じ%f
書式指定子を使用できますが、long double
後で作成された には異なる%Lf
書式指定子 (大文字 L
の) が必要です。