読者です 読者をやめる 読者になる 読者になる

Let's write β

趣味で書いたこととか、RustとLispが好き

Cの引き数のスタックへのつまれる順を利用して訳がわからない事を

C/C++

スタックに積まれる時は後ろの引き数から順番に評価するという事で

#include <stdio.h>

void showargs(int x, int y)
{
        printf("X: %d, Y: %d\n", x, y);
}

int main()
{
        int x = 0;
        showargs((printf("case1 x: %d\n", x), x = 1, x), x);
        x = 0;
        showargs((printf("case2 x: %d\n",x), x), ++x);
        return 0;
}

実行すると

case1 x: 0
X: 1, Y: 1
case2 x: 1
X: 1, Y: 1

となります、後ろから評価されていってるのは、case2の結果からわかるのですが、(++xの影響が前の引き数の時に発揮されている)、case1の結果はどうしてYが1として渡されているのでしょうか?値渡しなのでYの方には引き数が0として積まれてから前の引き数によってxが1になるので、
引き数はX:1, Y: 0になる物だとおもっていたのですが..
関数に渡される正確な動作を確認したいところです

追記
case1のときにもしかしたらxのアドレスが一度確保されて後からコピーされているのだろうかと思い別のテストケースを追加してみました。

#include <stdio.h>

void showargs(int x, int y)
{
        printf("X: %d, Y: %d\n", x, y);
}

int main()
{
        int x = 0;
        showargs((printf("case1 x: %d\n", x), x = 1, x), x);
        x = 0;
        showargs((printf("case2 x: %d\n",x), x), ++x);
        x = 0;
        showargs((printf("case3 x: %d\n",x), x = 1, x), x + 0);
        return 0;
}

x + 0とすることによってもし仮に評価されたとしたら、x + 0 => 0が値として直接得られるはずであり、xはその場での値になるかもしれないと思いました。
しかし、相変わらず

case1 x: 0
X: 1, Y: 1
case2 x: 1
X: 1, Y: 1
case3 x: 0
X: 1, Y: 1

このように前の引数を評価した段階ではxは0でありながらわたった段階ではxは1ということでyの引数が評価されているように見えます。。

最適化の問題かと思い

$ gcc --version
gcc (Debian 4.7.1-7) 4.7.1
Copyright (C) 2012 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ gcc -O0 -ansi arg.c

とコンパイルしていますが、結果は同じです。

さらに追記:
さらに最適化を抑制するためにxの宣言にvolatileをつけてみました。
すると!!

case1 x: 0
X: 1, Y: 0
case2 x: 1
X: 1, Y: 1
case3 x: 0
X: 1, Y: 0

このようにキチンとYが0としてわたるようになりました!!
おそらく最適化ですらなく、コンパイルの過程において「ユーザーはC言語の引数は前から評価されてると思っているだろう」とでも想定しているのでしょうか?
何しろ問題がはっきりしてくれて安心しました。

ついでにvolatileをつけたときとつけていないときでアセンブラーを出力しておきます。
後ほど比較してみましょう。
まずはvolatileがついていないほう

        .file   "arg.c"
        .section        .rodata
.LC0:
        .string "X: %d, Y: %d\n"
        .text
        .globl  showargs
        .type   showargs, @function
showargs:
.LFB0:
        .cfi_startproc
        pushl   %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        subl    $24, %esp
        movl    12(%ebp), %eax
        movl    %eax, 8(%esp)
        movl    8(%ebp), %eax
        movl    %eax, 4(%esp)
        movl    $.LC0, (%esp)
        call    printf
        leave
        .cfi_restore 5
        .cfi_def_cfa 4, 4
        ret
        .cfi_endproc
.LFE0:
        .size   showargs, .-showargs
        .section        .rodata
.LC1:
        .string "case1 x: %d\n"
.LC2:
        .string "case2 x: %d\n"
.LC3:
        .string "case3 x: %d\n"
        .text
        .globl  main
        .type   main, @function
main:
.LFB1:
        .cfi_startproc
        pushl   %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        andl    $-16, %esp
        subl    $32, %esp
        movl    $0, 28(%esp)
        movl    28(%esp), %eax
        movl    %eax, 4(%esp)
        movl    $.LC1, (%esp)
        call    printf
        movl    $1, 28(%esp)
        movl    28(%esp), %eax
        movl    %eax, 4(%esp)
        movl    28(%esp), %eax
        movl    %eax, (%esp)
        call    showargs
        movl    $0, 28(%esp)
        addl    $1, 28(%esp)
        movl    28(%esp), %eax
        movl    %eax, 4(%esp)
        movl    $.LC2, (%esp)
        call    printf
        movl    28(%esp), %eax
        movl    %eax, 4(%esp)
        movl    28(%esp), %eax
        movl    %eax, (%esp)
        call    showargs
        movl    $0, 28(%esp)
        movl    28(%esp), %eax
        movl    %eax, 4(%esp)
        movl    $.LC3, (%esp)
        call    printf
        movl    $1, 28(%esp)
        movl    28(%esp), %eax
        movl    %eax, 4(%esp)
        movl    28(%esp), %eax
        movl    %eax, (%esp)
        call    showargs
        movl    $0, %eax
        leave
        .cfi_restore 5
        .cfi_def_cfa 4, 4
        ret
        .cfi_endproc
.LFE1:
        .size   main, .-main
        .ident  "GCC: (Debian 4.7.1-7) 4.7.1"
        .section        .note.GNU-stack,"",@progbits

そしてついているほう:

        .file   "arg.c"
        .section        .rodata
.LC0:
        .string "X: %d, Y: %d\n"
        .text
        .globl  showargs
        .type   showargs, @function
showargs:
.LFB0:
        .cfi_startproc
        pushl   %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        subl    $24, %esp
        movl    12(%ebp), %eax
        movl    %eax, 8(%esp)
        movl    8(%ebp), %eax
        movl    %eax, 4(%esp)
        movl    $.LC0, (%esp)
        call    printf
        leave
        .cfi_restore 5
        .cfi_def_cfa 4, 4
        ret
        .cfi_endproc
.LFE0:
        .size   showargs, .-showargs
        .section        .rodata
.LC1:
        .string "case1 x: %d\n"
.LC2:
        .string "case2 x: %d\n"
.LC3:
        .string "case3 x: %d\n"
        .text
        .globl  main
        .type   main, @function
main:
.LFB1:
        .cfi_startproc
        pushl   %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        pushl   %ebx
        andl    $-16, %esp
        subl    $32, %esp
        .cfi_offset 3, -12
        movl    $0, 28(%esp)
        movl    28(%esp), %ebx
        movl    28(%esp), %eax
        movl    %eax, 4(%esp)
        movl    $.LC1, (%esp)
        call    printf
        movl    $1, 28(%esp)
        movl    28(%esp), %eax
        movl    %ebx, 4(%esp)
        movl    %eax, (%esp)
        call    showargs
        movl    $0, 28(%esp)
        movl    28(%esp), %eax
        addl    $1, %eax
        movl    %eax, %ebx
        movl    %ebx, 28(%esp)
        movl    28(%esp), %eax
        movl    %eax, 4(%esp)
        movl    $.LC2, (%esp)
        call    printf
        movl    28(%esp), %eax
        movl    %ebx, 4(%esp)
        movl    %eax, (%esp)
        call    showargs
        movl    $0, 28(%esp)
        movl    28(%esp), %ebx
        movl    28(%esp), %eax
        movl    %eax, 4(%esp)
        movl    $.LC3, (%esp)
        call    printf
        movl    $1, 28(%esp)
        movl    28(%esp), %eax
        movl    %ebx, 4(%esp)
        movl    %eax, (%esp)
        call    showargs
        movl    $0, %eax
        movl    -4(%ebp), %ebx
        leave
        .cfi_restore 5
        .cfi_restore 3
        .cfi_def_cfa 4, 4
        ret
        .cfi_endproc
.LFE1:
        .size   main, .-main
        .ident  "GCC: (Debian 4.7.1-7) 4.7.1"
        .section        .note.GNU-stack,"",@progbits

diffをとると

47a48
>       pushl   %ebx
49a51
>       .cfi_offset 3, -12
50a53
>       movl    28(%esp), %ebx
57,58c60
<       movl    %eax, 4(%esp)
<       movl    28(%esp), %eax
---
>       movl    %ebx, 4(%esp)
62c64,67
<       addl    $1, 28(%esp)
---
>       movl    28(%esp), %eax
>       addl    $1, %eax
>       movl    %eax, %ebx
>       movl    %ebx, 28(%esp)
68,69c73
<       movl    %eax, 4(%esp)
<       movl    28(%esp), %eax
---
>       movl    %ebx, 4(%esp)
72a77
>       movl    28(%esp), %ebx
79,80c84
<       movl    %eax, 4(%esp)
<       movl    28(%esp), %eax
---
>       movl    %ebx, 4(%esp)
83a88
>       movl    -4(%ebp), %ebx
85a91
>       .cfi_restore 3

僕が働いているAzit.incでは一緒に働けるエンジニアを募集しています!
採用情報 — 株式会社アジット|Azit Inc.