LoginSignup
4
0

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-06-10

背景

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

検証用コード

#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側の後者2つは毎回変わる

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環境

# 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環境

# 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相当で表示すると推察される。

もうちょっと試した

元のポストの後にご本人がポストされている以下の状況を追う。

#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

アセンブラ

// テキスト領域
.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環境で実行すると予期しないことが起こる。

4
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0