分析一道很难的c语言题

前置知识(副作用和序列点部分)

IMG_20230228_180844444

IMG_20230228_180851760

源码一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#define _CRT_SECURE_NO_WARNINGS 1;
#include <stdio.h>
int fun(int i)
{
i = i < 3 ? i++ : 0;
return i;
}
int main(void)
{
int i = 5, k, j;
k = fun(i / 2);
printf("%d,", k);
k = fun(i = i / 2);
printf("%d,", k);
k = fun(i / 2);
printf("%d\n", k);
k = k < 3 ? k++ : 0;

return 0;
}

输出结果

image-20230228174700353

看到这样的输出结果很让人迷惑,难道是因为底层实现中,i++的副作用先于赋值运算产生了吗?按照这个结论的话,执行代码i = i++;之后,i的值会保持不变,于是再去测试

测试源码二

1
2
3
4
5
6
7
8
9
int main(void)
{
int i = 2;

i = i++;
printf("%d", i);

return 0;
}

测试结果

image-20230228180651139

结果说明i++的副作用并不一定会在赋值运算符=之前发生,不过它到底在什么时候发生呢?我查看了C primer,在里面找到了答案,对于这次这个题来说,i++的副作用可以先于赋值运算符之前发生,也可以在之后发生,两种情况都没有错,具体取决于编译器的实现,用书中的话来讲"对于这种情况更精确地说,结果是未定义的,这意味着c标准并未定义结果是什么。"

书中的解释

image-20230228181857619

反汇编源码一

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
	i = i < 3 ? i++ : 0;
00721875 cmp dword ptr [i],3
00721879 jge __$EncStackInitStart+33h (072188Fh)
---------------------我想这里是赋值运算的开始部分,先对i进行了备份------------
0072187B mov eax,dword ptr [i]
0072187E mov dword ptr [ebp-0C4h],eax
---------------------------------------------------------------------
---------------------从这里开始,我想就是i++的部分-----------------
00721884 mov ecx,dword ptr [i]
00721887 add ecx,1
0072188A mov dword ptr [i],ecx
---------------------到这里,i++的副作用也已经产生了-----------------
0072188D jmp __$EncStackInitStart+3Dh (0721899h)
0072188F mov dword ptr [ebp-0C4h],0
----------------------把i的备份重新赋值给i,覆盖了i++的值------------
00721899 mov edx,dword ptr [ebp-0C4h]
0072189F mov dword ptr [i],edx
-----------------------------------------------------------------
return i;
007218A2 mov eax,dword ptr [i]
}
007218A5 pop edi
007218A6 pop esi
007218A7 pop ebx
007218A8 add esp,0C4h
007218AE cmp ebp,esp
007218B0 call __RTC_CheckEsp (0721244h)
007218B5 mov esp,ebp
007218B7 pop ebp
007218B8 ret
--- 无源文件 ----------------------------------------

反汇编源码二

1
2
3
4
5
6
7
8
9
10
	i = i++;
--------------------------赋值运算符的副作用--------------
00B8187C mov eax,dword ptr [i]
00B8187F mov dword ptr [i],eax
-------------------------赋值运算符的副作用完毕-------------
-------------------------可以看出源码二中i++的副作用后于赋值运算符了,跟源码一中正好相反----------
00B81882 mov ecx,dword ptr [i]
00B81885 add ecx,1
00B81888 mov dword ptr [i],ecx
----------------------------------------------------------------------------------------

总结:这次遇见的题目的确很难,不过我想这个问题不太正统,因为在C Primer中,作者已经声明了:如果一个变量多次出现在一个表达式中,不要对该变量使用递增或递减运算符。