C 语言发展史
C 语言是从两种语言 BCPL 和 B.BCPL 语言演变而来的。BCPL 是由马丁.理查兹在 1967年开发的,作为编写操作系统软件和编译器的语言。肯·汤普逊用他的 B 语言模拟了 BCPL 中的许多功能,1970年,他使用B语言在贝尔实验室创建了 UNIX 操作系统的早期版本。BCPL和B语言都是 “无数据类型” 语言,每个数据项都占据一个 “字” 大小,如果程序想要使用其他的数据类型就比较麻烦。
C语言是由贝尔实验室丹尼斯.里奇从 B 进化而来的,最初在 1972 年在 DDCPDP-11 计算机上实现。C 语言使用了 BCPL 和 B 语言中的许多概念,同时添加了数据类型和其他强大的特性。C 最初广泛认为是 UNIX 操作系统的开发语言。今天,几乎所有新的主要操作系统大多数是使用 C/C++ 编写的。C 主要是硬件独立的。通过仔细的设计,就可以编写适用于大多数计算机的 C 程序。
到 20 世纪 79 年代末,C 已经演变成现在被称为 “传统C”。1978 年,柯尼汉和丹尼斯里奇的书 《The C Programming Language》 的出版,引起了人们对这种语言的广泛关注。这就成为了有史以来最成功的计算机科学书籍之一。
C 语言在各类型的计算机上快速扩展,这也导致了许多类似但往往不兼容的改动。对于那些需要开发能够在多个平台上运行代码的程序员来说,这是一个严重的问题。很明显,C语言要定一个标准,让语言规范化。
1983年,美国成立了一个技术委员会,主要是 “提供一个明确的和计算机独立的语言定义”。1989年获得批准,该标准于 1999 年更新发布C99。C99 是 C 语言的修订标准,并不是所有流行的 C 编译器都支持 C99。
2011年:ISO发布了C语言的最新标准,称为C11。C11标准增加了一些新的特性和标准库函数,如泛型选择、多线程支持、原子操作和_Static_assert等。
C 标准库
在我们之前写的 386 汇编中,里面调用的函数基本都是 C 语言标准库或者windows API 的函数。一下列出常用的C/C++语言标准库的头文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <assert.h> #include <ctype.h> #include <errno.h> #include <float.h> #include <fstream.h> #include <iomanip.h> #include <iostream.h> #include <limits.h> #include <locale.h> #include <math.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <strstrea.h> #include <time.h> #include <wchar.h> #include <wctype.h>
标准C++(同上的不再注释)
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 #include <algorithm> #include <bitset> #include <cctype> #include <cerrno> #include <clocale> #include <cmath> #include <complex> #include <cstdio> #include <cstdlib> #include <cstring> #include <ctime> #include <deque> #include <exception> #include <fstream> #include <functional> #include <limits> #include <list> #include <map> #include <iomanip> #include <ios> #include <iosfwd> #include <iostream> #include <istream> #include <ostream> #include <queue> #include <set> #include <sstream> #include <stack> #include <stdexcept> #include <streambuf> #include <string> #include <utility> #include <vector> #include <cwchar> #include <cwctype>
C99 增加
1 2 3 4 5 6 7 8 9 #include <complex.h> #include <fenv.h> #include <inttypes.h> #include <stdbool.h> #include <stdint.h> #include <tgmath.h> #include <conio.h> #include <sio.h> #include <slib.h>
显示计算结果
计算整数的和并显示结果
计算整数 15 和 37的和,并显示结果。
代码清单1-1
1 2 3 4 5 6 7 8 9 10 11 #include <stdio.h> int main (void ) { printf ("%d" , 15 + 37 ); return 0 ; }
反汇编(debug模式)
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 00 C21840 push ebp 00 C21841 mov ebp,esp 00 C21843 sub esp,0 C0h 00 C21849 push ebx 00 C2184A push esi 00 C2184B push edi 00 C2184C mov edi,ebp00 C2184E xor ecx,ecx 00 C21850 mov eax,0 CCCCCCCCh 00 C21855 rep stos dword ptr es:[edi]00 C21857 mov ecx,offset _FC42537F_mjc@cpp (0 C2C008h) 00 C2185C call @__CheckForDebuggerJustMyCode@4 (0 C21316h) 00 C21861 push 34 h 00 C21863 push offset string "%d" (0 C27B30h) 00 C21868 call _printf (0 C210CDh) 00 C2186D add esp,8 00 C21870 xor eax,eax 00 C21872 pop edi 00 C21873 pop esi 00 C21874 pop ebx 00 C21875 add esp,0 C0h00 C2187B cmp ebp,esp 00 C2187D call __RTC_CheckEsp (0 C2123Fh)00 C21882 mov esp,ebp00 C21884 pop ebp 00 C21885 ret
反汇编(release模式)
1 2 3 4 5 6 7 8 9 10 00E91040 push 34 h 00E91042 push offset string "%d" (0E92100 h) 00E91047 call printf (0E91010 h) 00E9104C add esp,8 00E9104F xor eax,eax 00E91051 ret
实模式下直接把没有用的堆栈架构代码去掉了。本程序用的是 VS2022,不同版本的编译器,调试结果都有所不同。
硬编码
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 55 8B EC 81 EC C0 00 00 00 53 56 57 8B FD 33 C9 B8 CC CC CC CC F3 AB B9 08 C0 DB 00 E8 B5 FA FF FF 6 A 34 68 30 7B DB 00 E8 60 F8 FF FF 83 C4 08 33 C0 5F 5 E 5B 81 C4 C0 00 00 00 3B EC E8 BD F9 FF FF 8B E5 5 D C3
printf函数
printf格式化字符串漏洞
上面代码中的 %d 是格式化字符串,用来说明后续的参数要输出什么样类型的数据,常见的格式化字符串如下:
使用 vs6.0 编写如下程序:
1 2 3 4 5 6 7 8 9 #include <stdio.h> int main (void ) { int s = 0 ; printf ("the value of s is %n\n" , &s); printf ("%d\n" , s); return 0 ; }
输出的字符串长度,包括空格符 "the value of s is " 一共是18个字符,执行完第一个printf指令后,s变量的值变为18 。
在现在新的编译器中是不允许使用 “%n” 格式化字符来为变量赋值。
printf 函数的格式化字符串可以写任意多个,每一个格式化字符串对应一个要输出的参数。如果格式化字符串数量多于后面的参数数量,这就会导致字符串格式化漏洞,泄露堆栈信息。案例如下:
动态调试查看堆栈状态:
可以看到我们最后一个 “%d” 输出的是 ebp 的值。
利用该漏洞打印 call mian 函数的EIP指针
动态调试进入到 main 函数内,并计算堆栈大小:
0x40=64(Byte)=16(DWORD)
16+4=20(DWORD)
一共是20个字大小的堆栈空间,我们想要打印 call main 后的EIP,就需要再多打印一个 %x。
401219 的上一个值就是 ebp 的值,401219 对应的指针就是当前输出的值 [ebp+4] = 12ffc4
我这里多打印了一个 “%x”。
计算并显示整数的差
printf 函数中使用减法运算也很方便。
代码清单1-2
1 2 3 4 5 6 7 8 9 10 11 12 #include <stdio.h> #include <stdlib.h> int main (void ) { printf ("%d" , 15 - 37 ); return 0 ; system("pause" ); }
格式化字符串的转换与说明
把 printf 函数第一个实参设置更长复杂一些。
代码清单1-3
1 2 3 4 5 6 7 8 9 10 11 12 #include <stdio.h> #include <stdlib.h> int main (void ) { printf ("15减去37的结果是:%d\n" , 15 - 37 ); return 0 ; system("pause" ); }
无格式化输出
调用 printf 函数的时候也可以使用一个参数。这时,格式化字符串内的字符将按照原样显示。
代码清单1-4
1 2 3 4 5 6 7 8 9 10 11 #include <stdio.h> int main (void ) { printf ("你好!我叫9unk。\n" ); return 0 ; }
代码清单1-5
1 2 3 4 5 6 7 8 9 10 11 #include <stdio.h> int main (void ) { printf ("你好!\n我叫9unk。\n" ); return 0 ; }
代码清单1-6
1 2 3 4 5 6 7 8 9 10 11 12 #include <stdio.h> int main (void ) { printf ("你好!\n" ); printf ("我叫9unk。\n" ); return 0 ; }
字符串常量: 向 “ABC” 和 “您好!” 这样用双引号 (") 括起来的一连串排列的文字,称为字符串常量。
转义字符
前面能够实现换行的特殊符号 \n,就是转义字符。响铃的转义字符是 \a
代码清单1-7
1 2 3 4 5 6 7 8 9 10 11 #include <stdio.h> int main (void ) { printf ("你好!\a\a\a\n" ); return 0 ; }
变量
为了记录下计算过程中的结果以及最终结果,需要使用变量。
变量和声明
变量的声明: 数据类型 变量名
代码清单1-8
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <stdio.h> int main (void ) { int x, y; x = 57 ; y = x + 10 ; printf ("X的值是%d。\n" ,x); printf ("Y的值是%d。\n" , y); return 0 ; }
反汇编(debug模式)
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 00 A244E0 push ebp 00 A244E1 mov ebp,esp 00 A244E3 sub esp,0 D8h 00 A244E9 push ebx 00 A244EA push esi 00 A244EB push edi 00 A244EC lea edi,[ebp-18 h] 00 A244EF mov ecx,6 00 A244F4 mov eax,0 CCCCCCCCh 00 A244F9 rep stos dword ptr es:[edi] 00 A244FB mov ecx,0 A2C008h 00 A24500 call 00 A21316 00 A24505 mov dword ptr [ebp-8 ],39 h00 A2450C mov eax,dword ptr [ebp-8 ] 00 A2450F add eax,0 Ah 00 A24512 mov dword ptr [ebp-14 h],eax00 A24515 mov eax,dword ptr [ebp-8 ] 00 A24518 push eax 00 A24519 push 0 A27CD0h 00 A2451E call 00 A210CD 00 A24523 add esp,8 00 A24526 mov eax,dword ptr [ebp-14 h] 00 A24529 push eax 00 A2452A push 0 A27BD0h 00 A2452F call 00 A210CD 00 A24534 add esp,8 00 A24537 xor eax,eax 00 A24539 pop edi 00 A2453A pop esi 00 A2453B pop ebx 00 A2453C add esp,0 D8h 00 A24542 cmp ebp,esp 00 A24544 call 00 A2123F 00 A24549 mov esp,ebp 00 A2454B pop ebp 00 A2454C ret
局部变量使用的是 [ebp-xxx]
局部变量+立即数:mov eax,[ebp-xxx],add eax,立即数
反汇编(release模式)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 007F 1040 6 A 39 push 39 h 007F 1042 68 00 21 7F 00 push offset string "X\xb5\xc4\xd6\xb5\xca\xc7%d\xa1\xa3\n" (07F 2100h) 007F 1047 E8 C4 FF FF FF call printf (07F 1010h) 007F104C 6A 43 push 43h 007F104E 68 10 21 7F 00 push offset string "Y\xb5\xc4\xd6\xb5\xca\xc7%d\xa1\xa3\n" (07F 2110h) 007F1053 E8 B8 FF FF FF call printf (07F 1010h) 007F1058 83 C4 10 add esp,10h 007F105B 33 C0 xor eax,eax 007F105D C3 ret
实模式下直接把堆栈框架删除,把printf函数所需的值直接带入调用(运算结果在编译的时候就已经完成)。
硬编码
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 55 8B EC 81 EC D8 00 00 00 53 56 57 8 D 7 D E8 B9 06 00 00 00 B8 CC CC CC CC F3 AB B9 08 C0 A2 00 E8 11 CE FF FF C7 45 F8 39 00 00 00 8B 45 F8 83 C0 0 A 89 45 EC 8B 45 F8 50 68 D0 7 C A2 00 E8 AA CB FF FF 83 C4 08 8B 45 EC 50 68 D0 7B A2 00 E8 99 CB FF FF 83 C4 08 33 C0 5F 5 E 5B 81 C4 D8 00 00 00 3B EC E8 F6 CC FF FF 8B E5 5 D C3
赋值: 本程序中的等号 “=”,表示把右侧的值赋给左侧的变量。
初始化
尝试删除变量的赋值。
代码清单1-9
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <stdio.h> #include <stdio.h> int main (void ) { int x, y; printf ("X的值是%d。\n" ,x); printf ("Y的值是%d。\n" , y); return 0 ; }
变量 x 和 y 变成奇怪的值。这是因为在生成变量的时候,变量会被放入一个不确定的值,即 垃圾值 。
新的 C 编译器中已经不允许这样写了。我这里使用 VS6.0 进行演示。
声明时初始化
如果事先已经知道了变量中要存放的值,就应该首先将该值赋给变量。在声明变量的时候,除了有特别要求之外,一定要对其进行初始化。
代码清单1-10
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <stdio.h> int main (void ) { int x = 57 ; int y = x + 10 ; printf ("X的值是%d。\n" ,x); printf ("Y的值是%d。\n" , y); return 0 ; }
反汇编(debug模式)
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 009 C44E0 55 push ebp 009 C44E1 8B EC mov ebp,esp 009 C44E3 81 EC D8 00 00 00 sub esp,0 D8h 009 C44E9 53 push ebx 009 C44EA 56 push esi 009 C44EB 57 push edi 009 C44EC 8 D 7 D E8 lea edi,[ebp-18 h] 009 C44EF B9 06 00 00 00 mov ecx,6 009 C44F4 B8 CC CC CC CC mov eax,0 CCCCCCCCh 009 C44F9 F3 AB rep stos dword ptr es:[edi] 009 C44FB B9 08 C0 9 C 00 mov ecx,offset _FC42537F_mjc@cpp (09 CC008h) 009 C4500 E8 11 CE FF FF call @__CheckForDebuggerJustMyCode@4 (09 C1316h) 009 C4505 C7 45 F8 39 00 00 00 mov dword ptr [x],39 h 009 C450C 8B 45 F8 mov eax,dword ptr [x] 009 C450F 83 C0 0 A add eax,0 Ah 009 C4512 89 45 EC mov dword ptr [y],eax 009 C4515 8B 45 F8 mov eax,dword ptr [x] 009 C4518 50 push eax 009 C4519 68 D0 7 C 9 C 00 push offset string "X\xb5\xc4\xd6\xb5\xca\xc7%d\xa1\xa3\n" (09 C7CD0h) 009 C451E E8 AA CB FF FF call _printf (09 C10CDh) 009 C4523 83 C4 08 add esp,8 009 C4526 8B 45 EC mov eax,dword ptr [y] 009 C4529 50 push eax 009 C452A 68 D0 7B 9 C 00 push offset string "Y\xb5\xc4\xd6\xb5\xca\xc7%d\xa1\xa3\n" (09 C7BD0h) 009 C452F E8 99 CB FF FF call _printf (09 C10CDh) 009 C4534 83 C4 08 add esp,8 009 C4537 33 C0 xor eax,eax 009 C4539 5F pop edi 009 C453A 5 E pop esi 009 C453B 5B pop ebx 009 C453C 81 C4 D8 00 00 00 add esp,0 D8h 009 C4542 3B EC cmp ebp,esp 009 C4544 E8 F6 CC FF FF call __RTC_CheckEsp (09 C123Fh) 009 C4549 8B E5 mov esp,ebp 009 C454B 5 D pop ebp 009 C454C C3 ret
赋值和初始化都是使用 mov 指令传送立即数到 [ebp-xxx] 中。实模式下汇编代码都是一样的,这里就忽略了。
初始化和赋值: 初始化就是在生成变量时放入值。赋值就是在生成的变量中放入值。
输入和显示
介绍如何通过键盘输入的整数值,并将其存放在变量中。
通过键盘进行输入
读取一个整数值,并显示出来进行确认。
代码清单1-11
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main (void ) { int input; printf ("请输入一个整数值:" ); scanf ("%d" , &input); printf ("您输入的是%d。\n" , input); return 0 ; }
在新版的编译器中需要使用 “#define _CRT_SECURE_NO_WARNINGS” 来忽略告警信息。
反汇编(debug模式)
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 55 56 57 00081830 push ebp 00081831 mov ebp,esp 00081833 sub esp,0 D0h 00081839 push ebx 0008183 A push esi 0008183B push edi 0008183 C lea edi,[ebp-10 h] 0008183F mov ecx,4 00081844 mov eax,0 CCCCCCCCh 00081849 rep stos dword ptr es:[edi] 0008184B mov eax,dword ptr [__security_cookie (08 A020h)] 00081850 xor eax,ebp 00081852 mov dword ptr [ebp-4 ],eax 00081855 mov ecx,offset _FC42537F_mjc@cpp (08 C008h) 0008185 A call @__CheckForDebuggerJustMyCode@4 (081316 h) 0008185F push offset string "\xc7\xeb\xca\xe4\xc8\xeb\xd2\xbb\xb8\xf6\xd5\xfb\xca\xfd\xd6\xb5\xa3\xba" (087 CD0h) 00081864 call _printf (0810 CDh) 00081869 add esp,4 0008186 C lea eax,[input] 0008186F push eax 00081870 push offset string "%d" (087B D0h) 00081875 call _scanf (0813B 1h) 0008187 A add esp,8 0008187 D mov eax,dword ptr [input] 00081880 push eax 00081881 push offset string "\xc4\xfa\xca\xe4\xc8\xeb\xb5\xc4\xca\xc7%d\xa1\xa3\n" (087B D4h) 00081886 call _printf (0810 CDh) 0008188B add esp,8 0008188 E xor eax,eax 00081890 push edx 00081891 mov ecx,ebp 00081893 push eax 00081894 lea edx,ds:[818 C0h] 0008189 A call @_RTC_CheckStackVars@8 (0811E0 h) 0008189F pop eax 000818 A0 pop edx 000818 A1 pop edi 000818 A2 pop esi 000818 A3 pop ebx 000818 A4 mov ecx,dword ptr [ebp-4 ] 000818 A7 xor ecx,ebp 000818 A9 call @__security_check_cookie@4 (081145 h) 000818 AE add esp,0 D0h 000818B 4 cmp ebp,esp 000818B 6 call __RTC_CheckEsp (08123F h) 000818B B mov esp,ebp 000818B D pop ebp 000818B E ret
反汇编(release模式)
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 00E81080 push ebp 00E81081 mov ebp,esp 00E81083 sub esp,8 00E81086 mov eax,dword ptr [__security_cookie (0E83000 h)] 00E8108 B xor eax,ebp 00E8108 D mov dword ptr [ebp-4 ],eax 00E81090 push offset string "\xc7\xeb\xca\xe4\xc8\xeb\xd2\xbb\xb8\xf6\xd5\xfb\xca\xfd\xd6\xb5\xa3\xba" (0E82108 h) 00E81095 call printf (0E81020 h) 00E8109A lea eax,[input] 00E8109D push eax 00E8109E push offset string "%d" (0E8211 Ch) 00E810A3 call scanf (0E81050 h) 00E810A8 push dword ptr [input] 00E810AB push offset string "\xc4\xfa\xca\xe4\xc8\xeb\xb5\xc4\xca\xc7%d\xa1\xa3\n" (0E82120 h) 00E810B0 call printf (0E81020 h) 00E810B5 mov ecx,dword ptr [ebp-4] 00E810B8 add esp,14h 00E810BB xor ecx,ebp 00E810BD xor eax,eax 00E810BF call __security_check_cookie (0E810 C8h) 00E810C4 mov esp,ebp 00E810C6 pop ebp 00E810C7 ret
硬编码
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 55 push ebp 8B EC mov ebp,esp 81 EC D0 00 00 00 sub esp,0 D0h 53 push ebx 56 push esi 57 push edi 8 D 7 D F0 lea edi,[ebp-10 h] B9 04 00 00 00 mov ecx,4 B8 CC CC CC CC mov eax,0 CCCCCCCCh F3 AB rep stos dword ptr es:[edi] A1 20 A0 08 00 mov eax,dword ptr [__security_cookie (08 A020h)] 33 C5 xor eax,ebp 89 45 FC mov dword ptr [ebp-4 ],eax B9 08 C0 08 00 mov ecx,offset _FC42537F_mjc@cpp (08 C008h) E8 B7 FA FF FF call @__CheckForDebuggerJustMyCode@4 (081316 h) 68 D0 7 C 08 00 push offset string "\xc7\xeb\xca\xe4\xc8\xeb\xd2\xbb\xb8\xf6\xd5\xfb\xca\xfd\xd6\xb5\xa3\n" E8 64 F8 FF FF call _printf (0810 CDh) 83 C4 04 add esp,4 8 D 45 F4 lea eax,[input] 50 push eax 68 D0 7B 08 00 push offset string "%d" (087B D0h) E8 37 FB FF FF call _scanf (0813B 1h) 83 C4 08 add esp,8 8B 45 F4 mov eax,dword ptr [input] 50 push eax 68 D4 7B 08 00 push offset string "\xc4\xfa\xca\xe4\xc8\xeb\xb5\xc4\xca\xc7%d\xa1\xa3\n" (087B D4h) E8 42 F8 FF FF call _printf (0810 CDh) 83 C4 08 add esp,8 0 ;33 C0 xor eax,eax 52 push edx 8B CD mov ecx,ebp 50 push eax 8 D 15 C0 18 08 00 lea edx,ds:[818 C0h] E8 41 F9 FF FF call @_RTC_CheckStackVars@8 (0811E0 h) 58 pop eax 5 A pop edx 5F pop edi 5 E pop esi 5B pop ebx 8B 4 D FC mov ecx,dword ptr [ebp-4 ] 33 CD xor ecx,ebp E8 97 F8 FF FF call @__security_check_cookie@4 (081145 h) 81 C4 D0 00 00 00 add esp,0 D0h 3B EC cmp ebp,esp E8 84 F9 FF FF call __RTC_CheckEsp (08123F h) 8B E5 mov esp,ebp 5 D pop ebp C3 ret
格式化输入函数 scanf
scanf 可以同样像 printf 函数那样,通过转换说明 “%d” 来限制函数只能读取十进制数。需要注意的是 scanf 函数进行读取时,变量名前必须加上一个特定的特殊字符 “&”。
字符 “&” 在 C 语言中的意思就是便是后面跟着的变量的内存地址,也就是我们常说的指针。例如 ”&input“ 表示变量 input 变量的内存地址(指针)
乘法运算
读取一个整数,并显示其5倍数的值
代码清单1-12
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main (void ) { int input; printf ("请输入一个整数值:" ); scanf ("%d" , &input); printf ("它的5倍数是%d。\n" , input*5 ); return 0 ; }
反汇编
1 2 3 4 5 6 6B 45 F4 05 imul eax,dword ptr [input],5 50 push eax 68 D4 7B 0 C 00 push offset string "\xcb\xfc\xb5\xc45\xb1\xb6\xca\xfd\xca\xc7%d\xa1\xa3\n" (0 C7BD4h) E8 41 F8 FF FF call _printf (0 C10CDh) 83 C4 08 add esp,8
有符号数局部变量乘法:imul eax,[ebp-xxx],立即数
输出函数 puts
代码清单1-13
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main (void ) { int n1,n2; puts ("请输入两个整数。" ); printf ("整数1:" ); scanf ("%d" , &n1); printf ("整数2:" ); scanf ("%d" , &n2); printf ("它们的和是 %d。\n" , n1 + n2); return 0 ; }
反汇编
1 2 3 68 D0 7 C AA 00 push offset string "\xc7\xeb\xca\xe4\xc8\xeb\xc1\xbd\xb8\xf6\xd5\xfb\xca\xfd\xa1\xa3" (0 AA7CD0h) FF 15 80 B1 AA 00 call dword ptr [__imp__puts (0 AAB180h)] 83 C4 04 add esp,4
代码清单1-14
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main (void ) { int n1,n2; int sum; puts ("请输入两个整数。" ); printf ("整数1:" ); scanf ("%d" , &n1); printf ("整数2:" ); scanf ("%d" , &n2); sum = n1 + n2; printf ("它们的和是 %d。\n" , sum); return 0 ; }
反汇编
1 2 3 4 8B 45 F4 mov eax,dword ptr [ebp-0 Ch] 03 45 E8 add eax,dword ptr [ebp-18 h] 89 45 DC mov dword ptr [ebp-24 h],eax
局部变量相加并赋值反汇编:mov eax,[ebp-xxx];add eax,[ebp-xxx];mov [ebp-xxx],eax
总结
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> int main (void ) { int width; int height; puts ("求长方形的面积。" ); printf ("长:" ); scanf ("%d" , &width); printf ("宽:" ); scanf ("%d" , &height); printf ("面积是%d。\a\n" , width * height); return 0 ; }
硬编码
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 009E528 F 8B F4 mov esi,esp 009E5291 68 28 7 E 9 E 00 push offset string "\xc7\xf3\xb3\xa4\xb7\xbd\xd0\xce\xb5\xc4\xc3\xe6\xbb\xfd\xa1\xa3" (09E7 E28h) 009E5296 FF 15 70 B1 9 E 00 call dword ptr [__imp__puts (09 EB170h)] 009E529 C 83 C4 04 add esp,4 009E529 F 3B F4 cmp esi,esp 009E52 A1 E8 8 A BF FF FF call __RTC_CheckEsp (09E1230 h) 009E52 A6 68 D8 7B 9 E 00 push offset string "\xb3\xa4\xa3\xba" (09E7 BD8h) 009E52 AB E8 FC C0 FF FF call _printf (09E13 ACh) 009E52 B0 83 C4 04 add esp,4 009E52 B3 8 D 45 F4 lea eax,[width] 009E52 B6 50 push eax 009E52 B7 68 50 7 E 9 E 00 push offset string "%d" (09E7 E50h) 009E52 BC E8 22 C1 FF FF call _scanf (09E13 E3h) 009E52 C1 83 C4 08 add esp,8 009E52 C4 68 E0 7B 9 E 00 push offset string "\xbf\xed\xa3\xba" (09E7 BE0h) 009E52 C9 E8 DE C0 FF FF call _printf (09E13 ACh) 009E52 CE 83 C4 04 add esp,4 009E52 D1 8 D 45 E8 lea eax,[height] 009E52 D4 50 push eax 009E52 D5 68 50 7 E 9 E 00 push offset string "%d" (09E7 E50h) 009E52 DA E8 04 C1 FF FF call _scanf (09E13 E3h) 009E52 DF 83 C4 08 add esp,8 009E52 E2 8B 45 F4 mov eax,dword ptr [width] 009E52 E5 0F AF 45 E8 imul eax,dword ptr [height] 009E52 E9 50 push eax 009E52 EA 68 3 C 7 E 9 E 00 push offset string "\xc3\xe6\xbb\xfd\xca\xc7%d\xa1\xa3\x07\n" (09E7 E3Ch) 009E52 EF E8 B8 C0 FF FF call _printf (09E13 ACh) 009E52 F4 83 C4 08 add esp,8
课后练习
练习 1-1
1 2 3 4 5 6 7 8 9 10 11 #include <stdio.h> int main (void ) { printf ("15减去37的结果是%d\n" , 15 - 37 ); return 0 ; system("pause" ); }
练习 1-2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <stdio.h> int main (void ) { printf ("天\n\n地\n\n人\n" ); return 0 ; }
练习1-3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <stdio.h> int main (void ) { printf ("喂!\n\n您好!\n\n再见。\n" ); return 0 ; }
练习1-4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <stdio.h> int main (void ) { int x = 3.14 ; int y = 5.7 ; printf ("变量x=%d\n" , x); printf ("变量y=%d\n" , y); return 0 ; }
练习1-5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main (void ) { int input; printf ("请输入一个整数:" ); scanf ("%d" , &input); printf ("该值加12的结果:%d" , input + 12 ); return 0 ; }
练习1-6
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main (void ) { int input; printf ("请输入一个整数:" ); scanf ("%d" , &input); printf ("结果:%d - 6 = %d" ,input,input - 6 ); return 0 ; }
练习1-7
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <stdio.h> int main (void ) { puts ("天\n地\n人" ); return 0 ; }
练习1-8
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> int main (void ) { int x, y; puts ("请输入两个整数。" ); printf ("整数1:" ); scanf ("%d" , &x); printf ("整数2:" ); scanf ("%d" , &y); printf ("它们的乘积是%d\n" , x * y); return 0 ; }
练习1-9
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 #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> int main (void ) { int x, y, z; puts ("请输入两个整数。" ); printf ("整数1:" ); scanf ("%d" , &x); printf ("整数2:" ); scanf ("%d" , &y); printf ("整数3:" ); scanf ("%d" , &z); printf ("它们的和是%d\n" , x + y + z); return 0 ; }