64bit環境と32bit環境でlong longの出力結果が異なる事象の検証


# 背景
Twitterで以下のようなポストを見て、アーキテクチャの違いかな？と思ったが確証がなかったので試してみた。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">こどふぉお疲れ様でした…<br><br>Div2のB問題なんですけど，どこが間違ってるのか教えてくださる方がいらっしゃいましたらお願い致します．<br><br>ll := long long<br>IN(N) := cin &gt;&gt; N<br>です <a href="https://t.co/dgSMGliM2i">pic.twitter.com/dgSMGliM2i</a></p>&mdash; ふぉく@しうかつ (@D_A_works) <a href="https://twitter.com/D_A_works/status/1136998803859333121?ref_src=twsrc%5Etfw">2019年6月7日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


# 検証用コード
```c++
#include<stdio.h>
int main(){
 long long x=1, y=2, tmp1=3, tmp2=4;
 asm("nop");
 printf("%d %d %d %d\n", x, y); // ポイント: format上は4変数参照だが実際に関数に与えているのは2変数
 asm("nop");
 printf("%lld %lld %lld %lld\n", x, y);
 asm("nop");
}
```

# 32bit環境
`Linux debian32 4.9.0-9-686-pae #1 SMP Debian 4.9.168-1+deb9u2 (2019-05-13) i686 GNU/Linux`
での実行例

```
debian32:~$ ./a.out
1 0 2 0
1 2 -5228134716927016668 17184064983
debian32:~$ ./a.out
1 0 2 0
1 2 -5228768035623493692 17184490967
★lld側の後者２つは毎回変わる
```

# 64bit環境
`Linux www.hogetan.net 4.9.0-8-amd64 #1 SMP Debian 4.9.130-2 (2018-10-27) x86_64 GNU/Linux`

```
$ ./a.out
1 2 160 2103203760
1 2 2147483629 1
$ ./a.out
1 2 160 -1174779984
1 2 2147483628 1
```

# 比較
両環境で`gcc -W0 -S a.c`してアセンブラを出力して比較する

## 予備知識
```
MOVQ move Quad(=通常64bit)
MOVL move Long(=通常32bit)
```

## 32bit環境
```cpp
# text領域
.LC0:
        .string "%d %d %d %d\n"
.LC1:
        .string "%lld %lld %lld %lld\n"
# 代入部分
        movl    $1, -16(%ebp)
        movl    $0, -12(%ebp)
        movl    $2, -24(%ebp)
        movl    $0, -20(%ebp)
        movl    $3, -32(%ebp)
        movl    $0, -28(%ebp)
        movl    $4, -40(%ebp)
        movl    $0, -36(%ebp)
        subl    $12, %esp
# printf部分(1回目)
        pushl   -20(%ebp) // 4つめの%dのpop
        pushl   -24(%ebp) // 3つめの%dのpop
        pushl   -12(%ebp) // 2つめの%dのpop
        pushl   -16(%ebp) // 1つめの%dのpop
        leal    .LC0@GOTOFF(%ebx), %eax
        pushl   %eax
        call    printf@PLT
        addl    $32, %esp
# printf部分(2回目)
        subl    $12, %esp
        pushl   -20(%ebp) // 2つめの%lldのpop
        pushl   -24(%ebp) // 2つめの%lldのpop
        pushl   -12(%ebp) // 1つめの%lldのpop
        pushl   -16(%ebp) // 1つめの%lldのpop
        leal    .LC1@GOTOFF(%ebx), %eax
        pushl   %eax
        call    printf@PLT
        addl    $32, %esp
```

32bit環境では、long longを32bit x 2として扱っている。
そして、printfの引数として渡す際もxは`-20, -24`, yは`-12, -16`として渡そうとする。
だが、printfのライブラリ自身が、%dを指定された場合、32bit分しかpopせず、
%lldの場合、64bit分のpopをして表示していると推察される。

## 64bit環境

```cpp
# text領域
.LC0:
        .string "%d %d %d %d\n"
.LC1:
        .string "%lld %lld %lld %lld\n"
# 代入部分
        movq    $1, -8(%rbp)
        movq    $2, -16(%rbp)
        movq    $3, -24(%rbp)
        movq    $4, -32(%rbp)
# printf部分(1回目)
        movq    -16(%rbp), %rdx
        movq    -8(%rbp), %rax
        movq    %rax, %rsi
        leaq    .LC0(%rip), %rdi
        movl    $0, %eax
        call    printf@PLT
# printf部分(2回目)
        movq    -16(%rbp), %rdx
        movq    -8(%rbp), %rax
        movq    %rax, %rsi
        leaq    .LC1(%rip), %rdi
        movl    $0, %eax
        call    printf@PLT
```

64bit環境では、long longの変数はmovqを使って操作が行われている。
printfのライブラリは%dでも、%lldでも1回のpop相当で表示すると推察される。

# もうちょっと試した
元のポストの後にご本人がポストされている以下の状況を追う。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">x=1, y=1;<br><br>printf(“%d %d\n”, x, y)<br>だと出力が 1 0 でWA<br><br>printf(“%d “, x);<br>printf(“%d\n”, y);<br>だと出力が 1 1 で通った<br><br>なにこれ</p>&mdash; ふぉく@しうかつ (@D_A_works) <a href="https://twitter.com/D_A_works/status/1137003020732862465?ref_src=twsrc%5Etfw">2019年6月7日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

```cpp
#include<stdio.h>
int main(){
 long long x=1, y=2, tmp1=3, tmp2=4;
 asm("nop");
 printf("%d %d\n", x, y);
 asm("nop");
 printf("%d\n", x);
 asm("nop");
 printf("%d\n", y);
 asm("nop");
}
```

実行結果

```
$ ./a.out
1 0 2 0
1
2
```

アセンブラ

```cpp
// テキスト領域
.LC0:
        .string "%d %d\n"
.LC1:
        .string "%d\n"
// 代入
        movl    $1, -16(%ebp)
        movl    $0, -12(%ebp)
        movl    $2, -24(%ebp)
        movl    $0, -20(%ebp)
        subl    $12, %esp
// 1回目のprintf
        pushl   -20(%ebp) 
        pushl   -24(%ebp) // ほんとはここもpopしてほしいけどされない(32bit2つしかpopしないから)
        pushl   -12(%ebp) // ここが2つめの%dでpopされる = 0
        pushl   -16(%ebp) // ここが1つめの%dでpopされる = 1
        leal    .LC0@GOTOFF(%ebx), %eax
        pushl   %eax
        call    printf@PLT
        addl    $32, %esp
// 2回目のprintf
        subl    $4, %esp
        pushl   -12(%ebp)
        pushl   -16(%ebp) // ここが1つめの%dでpopされる = 1
        leal    .LC1@GOTOFF(%ebx), %eax
        pushl   %eax
        call    printf@PLT
        addl    $16, %esp
// 3回目のprintf
        subl    $4, %esp 
        pushl   -20(%ebp)
        pushl   -24(%ebp) // ここが1つめの%dでpopされる = 2
        leal    .LC1@GOTOFF(%ebx), %eax
        pushl   %eax
        call    printf@PLT
        addl    $16, %esp
```

以上の通り、
1つ目のprintfでは`%d %d`で`-12`と`-16`が用いられるが、
2,3つ目のprintfでは`%d`で`-12だけ`と`-24`が用いられて、予期する表示が得られる。

# まとめ
32bit環境ではprintfの%dは32bit分のpopしか行わないため、64bit環境で開発したコードを32bit環境で実行すると予期しないことが起こる。