スタックに積まれる時は後ろの引き数から順番に評価するという事で
#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