下面两段代码:
1 2 3 4 5 6
| #include<stdio.h>
int main(){ int p = 0; printf("%d %d\n",p++,++p); }
|
1 2 3 4 5 6
| #include<stdio.h>
int main(){ int p = 0; printf("%d %d\n",++p,p++); }
|
代码的输出,在Ubuntu中使用gcc编译器进行编译,上面的是1 2,下面的是2 0。
这就不得不提到自增表达式的值了,p++返回的值是做++操作前p的值,++p返回的是做完所有自增(++,+=之类的)语句之后p的值。然后是函数调用处理实参的方法,gcc是使用从右到左的顺序处理实参。第一段代码首先做++p,p变成1,然后做p++,p变成2,做p++的时候p为1,那么就返回1.第二段代码同理。两端代码都是做了两次++操作,所以在做完分号内所有语句之后p的值一定是2.
比如说这段代码
1 2 3 4 5 6
| #include<stdio.h>
int main(){ int p = 0; printf("%d\n",(++p)+(p++,++p)); }
|
返回值是6,(p++,++p)的返回值是逗号最右的值,而是3.
比如说这段代码
1 2 3 4 5 6
| #include<stdio.h>
int main(){ int p = 0; printf("%d\n",(p+=2)+(p++,++p)); }
|
返回值是8.
这说明了++p返回是有向上面阐述的那样的滞后性。当然正常人不可能会这样写C语言的代码,本文也只是探究一下这样的C语言ub。
下面我们来看看x86汇编之后的汇编语句。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| .file "1.c" .text .section .rodata .LC0: .string "%d %d\n" .text .globl main .type main, @function main: .LFB0: .cfi_startproc endbr64 pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 subq $16, %rsp movl $0, -4(%rbp) addl $1, -4(%rbp) movl -4(%rbp), %eax leal 1(%rax), %edx movl %edx, -4(%rbp) movl -4(%rbp), %edx movl %eax, %esi leaq .LC0(%rip), %rdi movl $0, %eax call printf@PLT movl $0, %eax leave .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0" .section .note.GNU-stack,"",@progbits .section .note.gnu.property,"a" .align 8 .long 1f - 0f .long 4f - 1f .long 5 0: .string "GNU" 1: .align 8 .long 0xc0000002 .long 3f - 2f 2: .long 0x3 3: .align 8 4:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| .file "2.c" .text .section .rodata .LC0: .string "%d %d\n" .text .globl main .type main, @function main: .LFB0: .cfi_startproc endbr64 pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 subq $16, %rsp movl $0, -4(%rbp) movl -4(%rbp), %eax leal 1(%rax), %edx movl %edx, -4(%rbp) addl $1, -4(%rbp) movl -4(%rbp), %ecx movl %eax, %edx movl %ecx, %esi leaq .LC0(%rip), %rdi movl $0, %eax call printf@PLT movl $0, %eax leave .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0" .section .note.GNU-stack,"",@progbits .section .note.gnu.property,"a" .align 8 .long 1f - 0f .long 4f - 1f .long 5 0: .string "GNU" 1: .align 8 .long 0xc0000002 .long 3f - 2f 2: .long 0x3 3: .align 8 4:
|
汇编语句一开始首先给main开了个临时变量栈。-4(%rbp)是p。然后对p进行自增操作,把p++应该返回的值传递给eax寄存器。这里汇编和我当时学汇编的时候不一样的点在于,在进行函数调用的时候,并没有按照栈帧进行传参。而是直接使用寄存器进行传参。rdi是第一个参数(地址量,因为是64位汇编),esi是第二个参数,edx是第三个参数。eax是返回值。