1.循环语句

  • while

  • for

  • do while

  • break:直接跳出循环

  • continue:跳过本次循环后面的代码进入下一次的判断

while循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
int main()
{
/* 局部变量定义 */
int a = 10;
/* while 循环执⾏ */
while (a < 20)
{
printf("a 的值: %d\n", a);
a++;
if (a > 15)
{
/* 使⽤ break 语句终⽌循环 */
break;
}
}
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
	int a = 10;
00007FF72566520B mov dword ptr [a],0Ah
/* while 循环执⾏ */
while (a < 20)
00007FF725665212 cmp dword ptr [a],14h
00007FF725665216 jge main+49h (07FF725665239h)
{
printf("a 的值: %d\n", a);
00007FF725665218 mov edx,dword ptr [a]
00007FF72566521B lea rcx,[string "a \xb5\xc4\xd6\xb5\xa3\xba %d\n" (07FF725669C10h)]
00007FF725665222 call printf (07FF72566118Bh)
a++;
00007FF725665227 mov eax,dword ptr [a]
00007FF72566522A inc eax
00007FF72566522C mov dword ptr [a],eax
if (a > 15)
00007FF72566522F cmp dword ptr [a],0Fh
00007FF725665233 jle main+47h (07FF725665237h)
{
/* 使⽤ break 语句终⽌循环 */
break;
00007FF725665235 jmp main+49h (07FF725665239h)
}
}
00007FF725665237 jmp main+22h (07FF725665212h)
return 0;
00007FF725665239 xor eax,eax
}

break 后直接跳出循环

for循环

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
int main()
{
/* for 循环执⾏ */
for (int a = 10; a < 20; a = a++)
{
printf("a 的值: %d\n", a);
}
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
/* for 循环执⾏ */
for (int a = 10; a < 20; a = a++)
00EC1865 mov dword ptr [ebp-8],0Ah
00EC186C jmp __$EncStackInitStart+31h (0EC187Dh)
00EC186E mov eax,dword ptr [ebp-8]
00EC1871 mov dword ptr [ebp-8],eax
00EC1874 mov ecx,dword ptr [ebp-8]
00EC1877 add ecx,1
00EC187A mov dword ptr [ebp-8],ecx
00EC187D cmp dword ptr [ebp-8],14h
00EC1881 jge __$EncStackInitStart+4Ah (0EC1896h)
{
printf("a 的值: %d\n", a);
00EC1883 mov eax,dword ptr [ebp-8]
00EC1886 push eax
00EC1887 push offset string "a \xb5\xc4\xd6\xb5\xa3\xba %d\n" (0EC7B30h)
00EC188C call _printf (0EC10CDh)
00EC1891 add esp,8
}
00EC1894 jmp __$EncStackInitStart+22h (0EC186Eh)
return 0;
00EC1896 xor eax,eax
}

do….while循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
int main()
{
/* 局部变量定义 */
int a = 10;
/* do 循环执⾏,在条件被测试之前⾄少执⾏⼀次 */
do
{
if (a == 15)
{
/* 跳过迭代 */
a = a + 1;
continue;
}
printf("a 的值: %d\n", a);
a = a++;
} while (a < 20);
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
	/* 局部变量定义 */
int a = 10;
00261865 mov dword ptr [a],0Ah
/* do 循环执⾏,在条件被测试之前⾄少执⾏⼀次 */
do
{
if (a == 15)
0026186C cmp dword ptr [a],0Fh
00261870 jne __$EncStackInitStart+31h (026187Dh)
{
/* 跳过迭代 */
a = a + 1;
00261872 mov eax,dword ptr [a]
00261875 add eax,1
00261878 mov dword ptr [a],eax
continue;
0026187B jmp __$EncStackInitStart+51h (026189Dh)
}
printf("a 的值: %d\n", a);
0026187D mov eax,dword ptr [a]
00261880 push eax
00261881 push offset string "a \xb5\xc4\xd6\xb5\xa3\xba %d\n" (0267B30h)
00261886 call _printf (02610CDh)
0026188B add esp,8
a = a++;
0026188E mov eax,dword ptr [a]
00261891 mov dword ptr [a],eax
00261894 mov ecx,dword ptr [a]
00261897 add ecx,1
0026189A mov dword ptr [a],ecx
} while (a < 20);
0026189D cmp dword ptr [a],14h
002618A1 jl __$EncStackInitStart+20h (026186Ch)
return 0;
002618A3 xor eax,eax
}

这里的循环都很简单,主要是要自己写出来后去反汇编一下。多看看这些反汇编后的代码。

2.结构体

结构体?

结构体就是数组升级版….可以自定义其中的数据类型。

定义

用 struct

1
2
3
4
5
6
7
8
struct tag {
member-list
member-list
member-list
...
} variable-list ;
//这样直接初始化
也可以在后续中 struct tag lll;
  • tag 是结构体的标签。也就是一个大名字
  • member-list 标准的变量定义。比如:int.. float.. char..
  • variable-list 结构体变量。【对象?】

比如:

1
2
3
4
5
6
7
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book;
1
2
3
4
5
6
7
8
9
struct
{
int a;
char b;
double c;
} s1;
//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//同时⼜声明了结构体变量s1
//这个结构体并没有标明其标签
1
2
3
4
5
6
7
8
struct SIMPLE
{
int a;
char b;
double c;
};
//⽤SIMPLE标签的结构体,另外声明了变量t1、t2、t3
struct SIMPLE t1, t2[20], *t3;
1
2
3
4
5
6
7
8
9
//也可以⽤typedef创建新类型
typedef struct
{
int a;
char b;
double c;
} Simple2;
//现在可以⽤Simple2作为类型声明新的结构体变量
Simple2 u1, u2[20], *u3;

结构体里面可以包含结构体哦~

1
2
3
4
5
6
//此结构体的声明包含了其他的结构体
struct COMPLEX
{
char string[100];
struct SIMPLE a;
};

结构体里面可以包含自己结构体类型的指针

1
2
3
4
5
6
//此结构体的声明包含了指向⾃⼰类型的指针
struct NODE
{
char string[100];
struct NODE *next_node;
};

如果2给结构体相互包含,则需要对其中一个结构体进行不完整声明

1
2
3
4
5
6
7
8
9
10
11
12
struct B; //对结构体B进⾏不完整声明
//结构体A中包含指向结构体B的指针
struct A{
struct B *partner;
//other members;
};
//结构体B中包含指向结构体A的指针,在A声明完后,B也随之进⾏声明
struct B
{
struct A *partner;
//other members;
};

结构体的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book = {"C 语⾔", "RYH", "编程语⾔", 123456};

int main()
{
printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n",
book.title, book.author, book.subject, book.book_id);
}

访问结构成员

访问的时候,用:结构体变量名.成员名字

1
book.title

结构题变量的储存原理

1.数据成员对齐。

在内存中各类型的数据会按照一定的规律在内存中存放,这样就是对齐。结构体所占的内存空间就是每个成员对齐后存放的时候所占用的字节数。

how?

数据的起始地址的值要求是某个数K的倍数,而K就是该数据类型的对齐模数。

why?

加快速度,提升读取速度。

2.计算结构体大小的计算方法

对齐模数是 #pragma pack 指定的数值与该数据成员自身长度相比较得到的数值较小者。该数据相对起始位置应该是对齐数模的整数倍。

数模A是:#pragma pack指定的数值和结构体内最大的基本数据类型成员长度相比得到的数值较小者

然后结构体的长度应该是数模A的整数倍

#pragma pack指令的作用就是改变编译器的默认对齐方式

image-20230507225004987

实例1.

image-20230507225800322

实例2.

image-20230507225914727

分析一手:第一题:

image-20230507231309207

这样就只占有了8个字节的位置。

int型开始的时候,需要是0,4,8,…就是比较后小的的倍数

char 啥都可以

short 的 是2个字节,所以需要是 0 2 4 6….【2的倍数】,这也就是这个5为什么要跳过的原因

第二题:

image-20230507231535494

这里 int a 开始的时候需要是数模的倍数【int 是4 默认是8 较小的是4 , 所以需要4的倍数。也就是这里空了3个格子的原因】

short c 这里开始的时候刚好的8【2的倍数】所以就不需要空。

从0~9整体是10。这个时候结构体也需要对齐。结构体的数模是:定义的最大和默认比较中最小的【也就是int 和默认的比较 也就是4】需要是这个数模的倍数,也就是4的倍数。所以需要扩大2个字节,变成12。

所以整个结构体的占有的空间是12个字节

3.函数调用约定

这个函数调用的约定,其实,我们再win32中已经有接触过。这里来讲一讲C语言的

在C语言中,假设我们有这样的一个函数

1
int function(int a,int b)

在调用的时候,只要用result = function(1,2)这样的方式就可以使用这个函数了。

在CPU中,计算机是如何知道这函数调用的参数呢?这里计算机就提供了一个称为栈的数据结构来支持参数传递

栈的简述:是一种先进后出的数据结构。这里就不再累赘了。

再参数的传递中,有2个很重要的问题

  • 当参数多于1个的时候,按照什么样的顺序压入堆栈【从左到右?从右到左?】
  • 函数调用会,由谁来把堆栈恢复原来的状态

函数几种调用约定

  • stdcall 【pascal】
  • cdecl
  • fastcall
  • thiscall
  • naked call

stdcall【pascal】

Standard Call 的缩写 ,C++的标准调用方式

再微软C++系类的C/C++编译器中,常用PASCAL宏来声明这个调用约定,类似的宏还有WINAPI和CALLBACK

一般WIN32的函数都是__stdcall

声明的语法为

1
2
3
4
5
int __stdcall function(int a, int b)

//参数从右往左压入堆栈
//函数自身清理堆栈
//函数名自动加前导的下划线,后面跟着一个@符号,其后跟着参数的尺寸

例子:

1
2
3
4
int __stdcall Plus(int a, int b)
{
return a + b;
}

对应的汇编代码:

1
2
3
4
5
6
7
push 2 
push 1
call @ILT+10(Plus) (0040100f)

函数内部:

ret 8 ;这句是call对应的,内平栈

cdecl

C Declaration 的缩写 ,C语言缺省的调用约定【C语言默认调用约定】

声明语法:

1
2
3
4
5
6
7
int function (int a ,int b) //不加修饰就是C调⽤约定
int __cdecl function(int a,int b)//明确指出C调⽤约定

//参数从右向左压⼊堆栈
//调⽤者负责清理堆栈
//C调⽤约定允许函数的参数的个数是不固定的,这也是C语⾔的⼀⼤特⾊。
//仅在函数名前加上⼀个下划线前缀,格式为_functionname

例子:

1
2
3
4
int __cdecl Plus(int a, int b)
{
return a + b;
}

对应的汇编代码:

1
2
3
4
push 2 
push 1
call @ILT+15(Plus) (00401014)
add esp,8 ;这个就是外⾯平衡堆栈,传⼊两个参数,且为int型4byte,所以是8

fastcall

快速的call , 所以会用寄存器

声明语法:

1
2
3
4
5
6
int fastcall function(int a,int b)
//函数的第⼀个和第⼆个DWORD参数(或者尺⼨更⼩的)通过ecx和edx传递,其他参数通过从右向左的顺序压栈
//函数⾃身清理堆栈
//函数名修改规则同stdcall:函数名⾃动加前导的下划线,后⾯紧跟⼀个@符号,其后紧跟着参数的尺⼨

//优先使用寄存器传入参数

例字1:

1
2
3
4
int __fastcall Plus(int a, int b)
{
return a + b;
}

对应的汇编代码:

1
2
3
4
5
mov edx,2 ;因为传⼊寄存器不需要平衡堆栈 
mov ecx,1
call @ILT+0(Plus) (00401005)

ret

例子2:

1
2
3
4
int __fastcall Plus4(int a, int b, int c, int d)
{
return a + b + c + d;
}

对应的汇编:

1
2
3
4
5
6
7
push 4 ;当参数⼤于两个时,编译器会倒着存⼊堆栈,剩两个存⼊寄存器⾥ 
push 3
mov edx,2
mov ecx,1
call @ILT+5(Plus) (0040100a)

ret 8 ;外平栈,平衡传入参数的一部分

thiscall

C++类成员函数缺省的调用约定

thiscall是唯一一个不能明确指定的函数修饰,因为thiscaall不是关键字。由于成员函数调用还有一个this指针。所以需要特殊处理

参数从右向左⼊栈

如果参数个数不确定,this指针在所有参数压栈后被压⼊堆栈。如果参数个数确定,

this指针通过ecx传递给被调⽤者。

如果参数个数不确定,调⽤者清理堆栈,否则函数⾃⼰清理堆栈

对于参数个数固定情况下,类似于stdcall,不定时则类似cdecl

naked call

很少见,自己百度。一般用于实模式驱动程序设计

函数调用约定的常见问题

如果定义的约定和使用的约定不一致,则将导致堆栈被破坏,导致严重问题

  • 函数原型声明和函数体定义不一致
  • DLL【动态链接库】导入函数的时候声明了不同的函数约定

调用函数的代码和被调用函数必须采用相同的函数的调用约定,这样程序才能正常的运行。