少女祈祷中...

下面两段代码:

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是返回值。