C の宣言あれこれと printf

昨日のエントリをちょっと検証しようかなと思って、以下のようなコードを書いた。

#include <stdio.h>

int main(int argc, char *argv[]) {
    unsigned i(2);
    printf("%d %d %d", unsigned long long int(1), i, long(3));
    return 0;
}

さあ問題。

どのような出力が得られるでしょうか。

正解は下を反転してね。


1 0 2 (ただし環境に依存する)

































なんでこうなるのか、というのは、printf に渡している unsigned long long int に答えがある。

long long int は C99 で正式に新設された 64bit の型だ。一方 printf に渡された引数は後ろからスタックに詰まれ、可変長引数はその型が何かわからないため、すべてポインタと同じ 32bit として処理されるので、リトルエンディアン環境では long lon int の上位 32bit 部分が二番目の %d に割り当てられてしまう、ということだ(アセンブラ出力などで検証してないので想像が大いに含まれるんだけど)。

そこでこのような場合にどうすればよいかというと、

#include <stdio.h>

int main(int argc, char *argv[]) {
    unsigned i(2);
    printf("%lld %d %d", unsigned long long int(1), i, long(3));
    return 0;
}

という風に %lld と書く。こうすることで、printf 内部でスタックから二回ポップし、long long int として処理してくれる。


ちなみに unsigned は unsigned int で long は long int 。int は省略可能なので long long int は long long と書ける。unsigned では省略したくないけど long では省略したいのは Java の血が濃いからだろうな。

なお、上記コードは cl ではコンパイルできるけど gcc ではできない。gcc では printf に unsigned long long int(1) を渡すとシンタックスエラーになる。これは gcc が標準に準拠してないのか、それとも cl が拡張されているのか。

#include <stdio.h>

int main(int argc, char *argv[]) {
    unsigned i(2);
    printf("%lld %d %d", (unsigned long long int)int(1), i, long(3));
    return 0;
}

上記なら大丈夫。だけど、これは結局 long long int の値が渡せないので、gcc が標準準拠してないのだろう(というより C99 に完全に準拠してない、という方が聞こえが良いだろう。long long int は C99 以前から存在していた型だし)。