C语言语法入门-初识C语言
9unk Lv5

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>    //STL通用算法
#include <bitset> //STL位集容器
#include <cctype>
#include <cerrno>
#include <clocale>
#include <cmath>
#include <complex> //复数类
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <deque> //STL双端队列容器
#include <exception> //异常处理
#include <fstream>
#include <functional> //STL定义运算函数(代替运算符)
#include <limits>
#include <list> //STL线性列表容器
#include <map> //STL映射容器
#include <iomanip>
#include <ios> //基本输入/输出支持
#include <iosfwd> //输入/输出系统使用的前置声明
#include <iostream>
#include <istream> //基本的输入流
#include <ostream> //基本的输出流
#include <queue> //STL队列容器
#include <set> //STL集合容器
#include <sstream> //基于字符串的流
#include <stack> //STL堆栈容器
#include <stdexcept> //标准异常类
#include <streambuf> //底层输入/输出支持
#include <string> //字符串类
#include <utility> //STL通用模板类
#include <vector> //STL动态数组容器
#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> //说明调用DOS控制台 I/O子程序的各个函数
#include <sio.h> //包含字符串库函数说明的头文件
#include <slib.h> //包含动态存储与释放函数头文件

显示计算结果

计算整数的和并显示结果

计算整数 15 和 37的和,并显示结果。

代码清单1-1

1
2
3
4
5
6
7
8
9
10
11
/*程序名:list0101.c*/
/*
显示整数 15 和 37 的和
*/
#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
// 提升栈顶
00C21840 push ebp
00C21841 mov ebp,esp
00C21843 sub esp,0C0h
00C21849 push ebx
00C2184A push esi
00C2184B push edi
00C2184C mov edi,ebp

// 初始化堆栈空间,填充 0xCC
00C2184E xor ecx,ecx
00C21850 mov eax,0CCCCCCCCh
00C21855 rep stos dword ptr es:[edi]

//C++ 的调试辅助函数,当/JMC被启用时,就会插入 __CheckForDebuggerJustMyCode 函数
00C21857 mov ecx,offset _FC42537F_mjc@cpp (0C2C008h)
00C2185C call @__CheckForDebuggerJustMyCode@4 (0C21316h)

// printf("%d", 15 + 37); 这里使用的是外平栈
00C21861 push 34h
00C21863 push offset string "%d" (0C27B30h)
00C21868 call _printf (0C210CDh)
00C2186D add esp,8

//return 0;
00C21870 xor eax,eax

//降低栈顶
00C21872 pop edi
00C21873 pop esi
00C21874 pop ebx
00C21875 add esp,0C0h

//检查堆栈是否平衡,降低栈顶后 esp == ebp
00C2187B cmp ebp,esp
00C2187D call __RTC_CheckEsp (0C2123Fh)

//不管堆栈有没有平衡,强制降低栈顶
00C21882 mov esp,ebp

//降低栈底,函数返回
00C21884 pop ebp
00C21885 ret

反汇编(release模式)

1
2
3
4
5
6
7
8
9
10
//	printf("%d", 15 + 37);

00E91040 push 34h
00E91042 push offset string "%d" (0E92100h)
00E91047 call printf (0E91010h)
00E9104C add esp,8

// return 0;
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                   //push        ebp  
8B EC //mov ebp,esp
81 EC C0 00 00 00 //sub esp,0C0h
53 //push ebx
56 //push esi
57 //push edi
8B FD //mov edi,ebp
33 C9 //xor ecx,ecx
B8 CC CC CC CC //mov eax,0CCCCCCCCh
F3 AB //rep stos dword ptr es:[edi]
B9 08 C0 DB 00 //mov ecx,offset _FC42537F_mjc@cpp (0DBC008h)
E8 B5 FA FF FF //call @__CheckForDebuggerJustMyCode@4 (0DB1316h)
6A 34 //push 34h
68 30 7B DB 00 //push offset string "%d" (0DB7B30h)
E8 60 F8 FF FF //call _printf (0DB10CDh)
83 C4 08 //add esp,8
33 C0 //xor eax,eax
5F //pop edi
5E //pop esi
5B //pop ebx
81 C4 C0 00 00 00 //add esp,0C0h
3B EC //cmp ebp,esp
E8 BD F9 FF FF //call __RTC_CheckEsp (0DB123Fh)
8B E5 //mov esp,ebp
5D //pop ebp
C3 //ret

printf函数

1
2

printf格式化字符串漏洞

上面代码中的 %d 是格式化字符串,用来说明后续的参数要输出什么样类型的数据,常见的格式化字符串如下:
3

使用 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 。

4

在现在新的编译器中是不允许使用 “%n” 格式化字符来为变量赋值。

printf 函数的格式化字符串可以写任意多个,每一个格式化字符串对应一个要输出的参数。如果格式化字符串数量多于后面的参数数量,这就会导致字符串格式化漏洞,泄露堆栈信息。案例如下:
5

动态调试查看堆栈状态:
6

可以看到我们最后一个 “%d” 输出的是 ebp 的值。

利用该漏洞打印 call mian 函数的EIP指针

动态调试进入到 main 函数内,并计算堆栈大小:
7

0x40=64(Byte)=16(DWORD)
16+4=20(DWORD)

一共是20个字大小的堆栈空间,我们想要打印 call main 后的EIP,就需要再多打印一个 %x。
8

401219 的上一个值就是 ebp 的值,401219 对应的指针就是当前输出的值 [ebp+4] = 12ffc4

我这里多打印了一个 “%x”。

计算并显示整数的差

printf 函数中使用减法运算也很方便。

代码清单1-2

1
2
3
4
5
6
7
8
9
10
11
12
/*程序名:list0102.c*/
/*
输出15-37的结果
*/
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
printf("%d", 15 - 37); //输出 15-37 的结果
return 0;
system("pause");
}

格式化字符串的转换与说明

把 printf 函数第一个实参设置更长复杂一些。

代码清单1-3

1
2
3
4
5
6
7
8
9
10
11
12
/*程序名:list0103.c*/
/*
输出15-37的结果
*/
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
printf("15减去37的结果是:%d\n", 15 - 37); //输出 15-37 的结果
return 0;
system("pause");
}

9

无格式化输出

调用 printf 函数的时候也可以使用一个参数。这时,格式化字符串内的字符将按照原样显示。

代码清单1-4

1
2
3
4
5
6
7
8
9
10
11
/*程序名:list0104.c*/
/*
打招呼并进行自我介绍
*/
#include <stdio.h>

int main(void)
{
printf("你好!我叫9unk。\n");
return 0;
}

代码清单1-5

1
2
3
4
5
6
7
8
9
10
11
/*程序名:list0105.c*/
/*
打招呼并进行自我介绍,分行显示
*/
#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
/*程序名:list0106.c*/
/*
打招呼并进行自我介绍,分行显示
*/
#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
/*程序名:list0107.c*/
/*
显示 "你好!" 之后响铃3次
*/
#include <stdio.h>

int main(void)
{
printf("你好!\a\a\a\n");
return 0;
}

变量

为了记录下计算过程中的结果以及最终结果,需要使用变量。

变量和声明

变量的声明: 数据类型 变量名
10

代码清单1-8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*程序名:list0108.c*/
/*
为两个变量赋整数值并显示。
*/
#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
00A244E0  push        ebp  
00A244E1 mov ebp,esp
00A244E3 sub esp,0D8h
00A244E9 push ebx
00A244EA push esi
00A244EB push edi
00A244EC lea edi,[ebp-18h]
00A244EF mov ecx,6
00A244F4 mov eax,0CCCCCCCCh
00A244F9 rep stos dword ptr es:[edi]
00A244FB mov ecx,0A2C008h
00A24500 call 00A21316

//x = 57;
00A24505 mov dword ptr [ebp-8],39h

//y = x + 10;
00A2450C mov eax,dword ptr [ebp-8]
00A2450F add eax,0Ah
00A24512 mov dword ptr [ebp-14h],eax

//printf("X的值是%d。\n",x);
00A24515 mov eax,dword ptr [ebp-8]
00A24518 push eax
00A24519 push 0A27CD0h
00A2451E call 00A210CD
00A24523 add esp,8

//printf("Y的值是%d。\n", y);
00A24526 mov eax,dword ptr [ebp-14h]
00A24529 push eax
00A2452A push 0A27BD0h
00A2452F call 00A210CD
00A24534 add esp,8

//return 0;
00A24537 xor eax,eax

00A24539 pop edi
00A2453A pop esi
00A2453B pop ebx
00A2453C add esp,0D8h
00A24542 cmp ebp,esp
00A24544 call 00A2123F
00A24549 mov esp,ebp
00A2454B pop ebp
00A2454C 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
//int x, y;
//x = 57;
//y = x + 10;
//printf("X的值是%d。\n",x);
007F1040 6A 39 push 39h
007F1042 68 00 21 7F 00 push offset string "X\xb5\xc4\xd6\xb5\xca\xc7%d\xa1\xa3\n" (07F2100h)
007F1047 E8 C4 FF FF FF call printf (07F1010h)

//printf("Y的值是%d。\n", y);
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" (07F2110h)
007F1053 E8 B8 FF FF FF call printf (07F1010h)
007F1058 83 C4 10 add esp,10h
//return 0;
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                   //push        ebp  
8B EC //mov ebp,esp
81 EC D8 00 00 00 //sub esp,0D8h
53 //push ebx
56 //push esi
57 //push edi
8D 7D E8 //lea edi,[ebp-18h]
B9 06 00 00 00 //mov ecx,6
B8 CC CC CC CC //mov eax,0CCCCCCCCh
F3 AB //rep stos dword ptr es:[edi]
B9 08 C0 A2 00 //mov ecx,offset _FC42537F_mjc@cpp (0A2C008h)
E8 11 CE FF FF //call @__CheckForDebuggerJustMyCode@4 (0A21316h)
C7 45 F8 39 00 00 00 //mov dword ptr [x],39h
8B 45 F8 //mov eax,dword ptr [x]
83 C0 0A //add eax,0Ah
89 45 EC //mov dword ptr [y],eax
8B 45 F8 //mov eax,dword ptr [x]
50 //push eax
68 D0 7C A2 00 //push offset string "X\xb5\xc4\xd6\xb5\xca\xc7%d\xa1\xa3\n" (0A27CD0h)
E8 AA CB FF FF //call _printf (0A210CDh)
83 C4 08 //add esp,8
8B 45 EC //mov eax,dword ptr [y]
50 //push eax
68 D0 7B A2 00 //push offset string "Y\xb5\xc4\xd6\xb5\xca\xc7%d\xa1\xa3\n" (0A27BD0h)
E8 99 CB FF FF //call _printf (0A210CDh)
83 C4 08 //add esp,8
33 C0 //xor eax,eax
5F //pop edi
5E //pop esi
5B //pop ebx
81 C4 D8 00 00 00 //add esp,0D8h
3B EC //cmp ebp,esp
E8 F6 CC FF FF //call __RTC_CheckEsp (0A2123Fh)
8B E5 //mov esp,ebp
5D //pop ebp
C3 //ret

赋值: 本程序中的等号 “=”,表示把右侧的值赋给左侧的变量。

初始化

尝试删除变量的赋值。

代码清单1-9

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

/*程序名:list0109.c*/
/*
在不为两个变量赋值的情况下显示。
*/
#include <stdio.h>

int main(void)
{
int x, y;
printf("X的值是%d。\n",x);
printf("Y的值是%d。\n", y);
return 0;
}

11

变量 x 和 y 变成奇怪的值。这是因为在生成变量的时候,变量会被放入一个不确定的值,即 垃圾值

12

新的 C 编译器中已经不允许这样写了。我这里使用 VS6.0 进行演示。

声明时初始化

如果事先已经知道了变量中要存放的值,就应该首先将该值赋给变量。在声明变量的时候,除了有特别要求之外,一定要对其进行初始化。

代码清单1-10

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*程序名:list0110.c*/
/*
对两个变量进行初始化并显示。
*/
#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
009C44E0 55                   push        ebp  
009C44E1 8B EC mov ebp,esp
009C44E3 81 EC D8 00 00 00 sub esp,0D8h
009C44E9 53 push ebx
009C44EA 56 push esi
009C44EB 57 push edi
009C44EC 8D 7D E8 lea edi,[ebp-18h]
009C44EF B9 06 00 00 00 mov ecx,6
009C44F4 B8 CC CC CC CC mov eax,0CCCCCCCCh
009C44F9 F3 AB rep stos dword ptr es:[edi]
009C44FB B9 08 C0 9C 00 mov ecx,offset _FC42537F_mjc@cpp (09CC008h)
009C4500 E8 11 CE FF FF call @__CheckForDebuggerJustMyCode@4 (09C1316h)
//int x = 57;
009C4505 C7 45 F8 39 00 00 00 mov dword ptr [x],39h

//int y = x + 10;
009C450C 8B 45 F8 mov eax,dword ptr [x]
009C450F 83 C0 0A add eax,0Ah
009C4512 89 45 EC mov dword ptr [y],eax

//printf("X的值是%d。\n",x);
009C4515 8B 45 F8 mov eax,dword ptr [x]
009C4518 50 push eax
009C4519 68 D0 7C 9C 00 push offset string "X\xb5\xc4\xd6\xb5\xca\xc7%d\xa1\xa3\n" (09C7CD0h)
009C451E E8 AA CB FF FF call _printf (09C10CDh)
009C4523 83 C4 08 add esp,8

//printf("Y的值是%d。\n", y);
009C4526 8B 45 EC mov eax,dword ptr [y]
009C4529 50 push eax
009C452A 68 D0 7B 9C 00 push offset string "Y\xb5\xc4\xd6\xb5\xca\xc7%d\xa1\xa3\n" (09C7BD0h)
009C452F E8 99 CB FF FF call _printf (09C10CDh)
009C4534 83 C4 08 add esp,8

//return 0;
009C4537 33 C0 xor eax,eax

009C4539 5F pop edi
009C453A 5E pop esi
009C453B 5B pop ebx
009C453C 81 C4 D8 00 00 00 add esp,0D8h
009C4542 3B EC cmp ebp,esp
009C4544 E8 F6 CC FF FF call __RTC_CheckEsp (09C123Fh)
009C4549 8B E5 mov esp,ebp
009C454B 5D pop ebp
009C454C 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
/*程序名:list0111.c*/
/*
显示并确认输入的整数值。
*/
#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,0D0h
00081839 push ebx
0008183A push esi
0008183B push edi
0008183C lea edi,[ebp-10h]
0008183F mov ecx,4
00081844 mov eax,0CCCCCCCCh
00081849 rep stos dword ptr es:[edi]
0008184B mov eax,dword ptr [__security_cookie (08A020h)]
00081850 xor eax,ebp
00081852 mov dword ptr [ebp-4],eax
00081855 mov ecx,offset _FC42537F_mjc@cpp (08C008h)
0008185A call @__CheckForDebuggerJustMyCode@4 (081316h)

//printf("请输入一个整数值:");
0008185F push offset string "\xc7\xeb\xca\xe4\xc8\xeb\xd2\xbb\xb8\xf6\xd5\xfb\xca\xfd\xd6\xb5\xa3\xba" (087CD0h)
00081864 call _printf (0810CDh)
00081869 add esp,4

//scanf("%d", &input);
0008186C lea eax,[input]
0008186F push eax
00081870 push offset string "%d" (087BD0h)
00081875 call _scanf (0813B1h)
0008187A add esp,8

//printf("您输入的是%d。\n", input);
0008187D 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" (087BD4h)
00081886 call _printf (0810CDh)
0008188B add esp,8

//return 0;
0008188E xor eax,eax

00081890 push edx
00081891 mov ecx,ebp
00081893 push eax
00081894 lea edx,ds:[818C0h]
0008189A call @_RTC_CheckStackVars@8 (0811E0h)
0008189F pop eax
000818A0 pop edx
000818A1 pop edi
000818A2 pop esi
000818A3 pop ebx
000818A4 mov ecx,dword ptr [ebp-4]
000818A7 xor ecx,ebp
000818A9 call @__security_check_cookie@4 (081145h)
000818AE add esp,0D0h
000818B4 cmp ebp,esp
000818B6 call __RTC_CheckEsp (08123Fh)
000818BB mov esp,ebp
000818BD pop ebp
000818BE 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 (0E83000h)]
00E8108B xor eax,ebp
00E8108D mov dword ptr [ebp-4],eax

//printf("请输入一个整数值:");
00E81090 push offset string "\xc7\xeb\xca\xe4\xc8\xeb\xd2\xbb\xb8\xf6\xd5\xfb\xca\xfd\xd6\xb5\xa3\xba" (0E82108h)
00E81095 call printf (0E81020h)

//scanf("%d", &input);
00E8109A lea eax,[input]
00E8109D push eax
00E8109E push offset string "%d" (0E8211Ch)
00E810A3 call scanf (0E81050h)

//printf("您输入的是%d。\n", input);
00E810A8 push dword ptr [input]
00E810AB push offset string "\xc4\xfa\xca\xe4\xc8\xeb\xb5\xc4\xca\xc7%d\xa1\xa3\n" (0E82120h)
00E810B0 call printf (0E81020h)

//return 0;
00E810B5 mov ecx,dword ptr [ebp-4]
00E810B8 add esp,14h
00E810BB xor ecx,ebp
00E810BD xor eax,eax
00E810BF call __security_check_cookie (0E810C8h)
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,0D0h
53 push ebx
56 push esi
57 push edi
8D 7D F0 lea edi,[ebp-10h]
B9 04 00 00 00 mov ecx,4
B8 CC CC CC CC mov eax,0CCCCCCCCh
F3 AB rep stos dword ptr es:[edi]
A1 20 A0 08 00 mov eax,dword ptr [__security_cookie (08A020h)]
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 (08C008h)
E8 B7 FA FF FF call @__CheckForDebuggerJustMyCode@4 (081316h)
68 D0 7C 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 (0810CDh)
83 C4 04 add esp,4
8D 45 F4 lea eax,[input]
50 push eax
68 D0 7B 08 00 push offset string "%d" (087BD0h)
E8 37 FB FF FF call _scanf (0813B1h)
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" (087BD4h)
E8 42 F8 FF FF call _printf (0810CDh)
83 C4 08 add esp,8
0;
33 C0 xor eax,eax
52 push edx
8B CD mov ecx,ebp
50 push eax
8D 15 C0 18 08 00 lea edx,ds:[818C0h]
E8 41 F9 FF FF call @_RTC_CheckStackVars@8 (0811E0h)
58 pop eax
5A pop edx
5F pop edi
5E pop esi
5B pop ebx
8B 4D FC mov ecx,dword ptr [ebp-4]
33 CD xor ecx,ebp
E8 97 F8 FF FF call @__security_check_cookie@4 (081145h)
81 C4 D0 00 00 00 add esp,0D0h
3B EC cmp ebp,esp
E8 84 F9 FF FF call __RTC_CheckEsp (08123Fh)
8B E5 mov esp,ebp
5D pop ebp
C3 ret

格式化输入函数 scanf

scanf 可以同样像 printf 函数那样,通过转换说明 “%d” 来限制函数只能读取十进制数。需要注意的是 scanf 函数进行读取时,变量名前必须加上一个特定的特殊字符 “&”。

13

字符 “&” 在 C 语言中的意思就是便是后面跟着的变量的内存地址,也就是我们常说的指针。例如 ”&input“ 表示变量 input 变量的内存地址(指针)

乘法运算

读取一个整数,并显示其5倍数的值

代码清单1-12

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*程序名:list0112.c*/
/*
读取一个整数并显示其5倍数的值
*/
#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
//printf("它的5倍数是%d。\n", input*5);
6B 45 F4 05 imul eax,dword ptr [input],5
50 push eax
68 D4 7B 0C 00 push offset string "\xcb\xfc\xb5\xc45\xb1\xb6\xca\xfd\xca\xc7%d\xa1\xa3\n" (0C7BD4h)
E8 41 F8 FF FF call _printf (0C10CDh)
83 C4 08 add esp,8

有符号数局部变量乘法:imul eax,[ebp-xxx],立即数

输出函数 puts

14

代码清单1-13

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*程序名:list0113.c*/
/*
显示读取到的两个整数的和
*/
#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 7C AA 00       push        offset string "\xc7\xeb\xca\xe4\xc8\xeb\xc1\xbd\xb8\xf6\xd5\xfb\xca\xfd\xa1\xa3" (0AA7CD0h)  
FF 15 80 B1 AA 00 call dword ptr [__imp__puts (0AAB180h)]
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
/*程序名:list0114.c*/
/*
显示读取到的两个整数的和
*/
#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
//sum = n1 + n2;
8B 45 F4 mov eax,dword ptr [ebp-0Ch]
03 45 E8 add eax,dword ptr [ebp-18h]
89 45 DC mov dword ptr [ebp-24h],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
/*程序名:summary.c*/
/*求长方形的面积*/

#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
//puts("求长方形的面积。");
009E528F 8B F4 mov esi,esp
009E5291 68 28 7E 9E 00 push offset string "\xc7\xf3\xb3\xa4\xb7\xbd\xd0\xce\xb5\xc4\xc3\xe6\xbb\xfd\xa1\xa3" (09E7E28h)
009E5296 FF 15 70 B1 9E 00 call dword ptr [__imp__puts (09EB170h)]
009E529C 83 C4 04 add esp,4
009E529F 3B F4 cmp esi,esp
009E52A1 E8 8A BF FF FF call __RTC_CheckEsp (09E1230h)

//printf("长:");
009E52A6 68 D8 7B 9E 00 push offset string "\xb3\xa4\xa3\xba" (09E7BD8h)
009E52AB E8 FC C0 FF FF call _printf (09E13ACh)
009E52B0 83 C4 04 add esp,4

//scanf("%d", &width);
009E52B3 8D 45 F4 lea eax,[width]
009E52B6 50 push eax
009E52B7 68 50 7E 9E 00 push offset string "%d" (09E7E50h)
009E52BC E8 22 C1 FF FF call _scanf (09E13E3h)
009E52C1 83 C4 08 add esp,8

//printf("宽:");
009E52C4 68 E0 7B 9E 00 push offset string "\xbf\xed\xa3\xba" (09E7BE0h)
009E52C9 E8 DE C0 FF FF call _printf (09E13ACh)
009E52CE 83 C4 04 add esp,4

//scanf("%d", &height);
009E52D1 8D 45 E8 lea eax,[height]
009E52D4 50 push eax
009E52D5 68 50 7E 9E 00 push offset string "%d" (09E7E50h)
009E52DA E8 04 C1 FF FF call _scanf (09E13E3h)
009E52DF 83 C4 08 add esp,8

//printf("面积是%d。\a\n", width * height);
009E52E2 8B 45 F4 mov eax,dword ptr [width]
009E52E5 0F AF 45 E8 imul eax,dword ptr [height]
009E52E9 50 push eax
009E52EA 68 3C 7E 9E 00 push offset string "\xc3\xe6\xbb\xfd\xca\xc7%d\xa1\xa3\x07\n" (09E7E3Ch)
009E52EF E8 B8 C0 FF FF call _printf (09E13ACh)
009E52F4 83 C4 08 add esp,8

课后练习

练习 1-1

15

1
2
3
4
5
6
7
8
9
10
11
/*程序名:lx1-1.c*/
/*
计算出15-37的结果,并输出 "15减去37的结果是-22."
*/
#include <stdio.h>
int main(void)
{
printf("15减去37的结果是%d\n", 15 - 37); //输出 15-37 的结果
return 0;
system("pause");
}

练习 1-2

16

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*程序名:lx1-2.c*/
/*
调用一次printf函数,显示右侧内容。 天




*/
#include <stdio.h>
int main(void)
{
printf("天\n\n地\n\n人\n");
return 0;
}

练习1-3

17

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*程序名:lx1-3.c*/
/*
调用一次printf函数,显示右侧内容。 喂!

您好!

再见。
*/
#include <stdio.h>
int main(void)
{
printf("喂!\n\n您好!\n\n再见。\n");
return 0;
}

练习1-4

18

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*程序名:lx1-4.c*/
/*
int型变量的声明中为变量赋一个实数值的初始值,小数部分会被删除,只保留整数部分
*/
#include <stdio.h>
int main(void)
{
int x = 3.14;
int y = 5.7; //int数据类型-整型,宽度4字节

printf("变量x=%d\n", x);
printf("变量y=%d\n", y);
return 0;
}

练习1-5

19

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*程序名:lx1-5.c*/
/*
读取一个整数并显示该整数加12之后的结果。
*/
#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

20

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*程序名:lx1-6.c*/
/*
读取一个整数并显示该整数减去6之后的结果。
*/
#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

21

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*程序名:lx1-7.c*/
/*
调用puts函数显示右侧内容。 天


*/
#include <stdio.h>

int main(void)
{
//puts 只输出字符串,且默认会加上换行符 "\n"
puts("天\n地\n人");
return 0;
}

练习1-8

22

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*程序名:lx1-8.c*/
/*
从键盘接收两个数字,并显示两个整数的乘积,输出格式如下:
请输入两个整数。
整数1:
整数2:
它们的乘积是
*/
#define _CRT_SECURE_NO_DEPRECATE
#include <stdio.h>

int main(void)
{
int x, y;
//puts 只输出字符串,且默认会加上换行符 "\n"
puts("请输入两个整数。");

printf("整数1:"); scanf("%d", &x);
printf("整数2:"); scanf("%d", &y);

printf("它们的乘积是%d\n", x * y);
return 0;
}

练习1-9

23

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
/*程序名:lx1-9.c*/
/*
从键盘接收三个数字,并显示三个整数的和,输出格式如下:
请输入三个整数。
整数1:
整数2:
整数3:
它们的和是
*/
#define _CRT_SECURE_NO_DEPRECATE
#include <stdio.h>

int main(void)
{
int x, y, z;
//puts 只输出字符串,且默认会加上换行符 "\n"
puts("请输入两个整数。");

printf("整数1:"); scanf("%d", &x);
printf("整数2:"); scanf("%d", &y);
printf("整数3:"); scanf("%d", &z);

printf("它们的和是%d\n", x + y + z);
return 0;
}
  • 本文标题:C语言语法入门-初识C语言
  • 本文作者:9unk
  • 创建时间:2023-05-04 21:00:00
  • 本文链接:https://9unkk.github.io/2023/05/04/c-yu-yan-yu-fa-ru-men-chu-shi-c-yu-yan/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!