C
一、创建一个项目
File -> New -> Projects -> win32 Console Application -> Ok -> A "Hello, World!" application -> Finish -> Ok -> FileView -> test.cpp
1、快捷键
F5 运行
F7 编译
F9 断点
shitg + F5 结束程序
2、关闭程序
File -> Close Workspace
3、打开一个文件
File -> Open Workspace
4、寄存器窗口
在程序执行时,界面上面右键点击Registers
5、内存窗口
在程序执行时,界面上面右键点击Memory
6、进入反汇编
在程序执行时,右键Go To Disassembly
7、再反汇编窗口去掉C语言
再反汇编窗口右键点Source Annotation
8、再反汇编窗口显示硬编码
再反汇编窗口右键点 Code Bytes
9、
反汇编中进入某个程序按F11(相当于汇编中的F7)
F10(相当于汇编中的F8)
二、函数
1、无参数,无返回值的函数格式
# void 代表这个函数什么都不返回,返回什么类型void就改成什么类型
void 函数名()
{
}
2、有参数,有返回值
int Plus1(int x,int y)
{
return x + y;
}
int main(int argc, char* argv[])
{
Plus1(1,2);
return 0;
}
3、函数调用函数
#include "stdafx.h"
int Plus1(int x,int y)
{
return x + y;
}
int Plus2(int x,int y,int z)
{
int t;
int r;
t = Plus1(x,y);
r = Plus1(t,z);
return r;
}
int main(int argc, char* argv[])
{
Plus2(1,2,3);
return 0;
}
4、裸函数
正常函数,编译器会自动帮我们生成一些东西,裸函数则是编译器啥都不生成
void __declspec(naked) Plus()
{
}
int main(int argc, char* argv[])
{
Plus();
return 0;
}
编译可以正常编译,但是执行会报错,原因是函数执行之后没有返回地址,需要手动添加
#include "stdafx.h"
void __declspec(naked) Plus()
{
# 在编译器中写汇编代码如下
__asm
{
ret
}
}
int main(int argc, char* argv[])
{
Plus();
return 0;
}
5、裸函数实现1+2
正常函数编译器会自动帮我们生成,以下代码,裸函数需要我们手动添加以下代码
#include "stdafx.h"
int __declspec(naked) Plus(int x,int y)
{
__asm
{
//保留调用函数前的栈底
push ebp
//提升堆栈
mov ebp,esp
sub esp,0x40
//保存现场
push ebx
push esi
push edi
//填充缓冲区
mov eax,0xcccccccc
mov ecx,0x10
Lea edi,dword ptr ds:[ebp-0x40]
rep stosd
//函数的核心功能,两个参数相加
mov eax,dword ptr ds:[ebp+0x8]
add eax,dword ptr ds:[ebp+0xc]
//恢复现场
pop edi
pop esi
pop ebx
//降低堆栈
mov esp,ebp
pop ebp
ret
}
}
int main(int argc, char* argv[])
{
Plus(1,2);
return 0;
}
三、调用约定
调用约定 | 参数压栈顺序 | 平衡堆栈 |
---|---|---|
__cdecl | 从右至左入栈 | 调用者清理栈 |
__stdcall | 从右至左入栈 | 自身清理堆栈 |
__fastcall | ECX/EDX传送前两个,剩下:从右至左入栈 | 自身清理堆栈 |
1、__cdecl(编译器默认调用,外平栈)
#include "stdafx.h"
int __cdecl Plus1(int a, int b)
{
return a+b;
}
int main(int argc, char* argv[])
{
Plus1(1,2);
return 0;
}
2、__stdcall(内平栈)
#include "stdafx.h"
int __stdcall Plus2(int a, int b)
{
return a+b;
}
int main(int argc, char* argv[])
{
Plus2(1,2);
return 0;
}
3、__fastcall
当传递的参数为2个时,用寄存器传递,速度较快,且不需要平栈
当参数多余两个时,与其它两种调用方式没啥区别了,在函数里面进行平栈
#include "stdafx.h"
int __fastcall Plu3(int a, int b)
{
return a+b;
}
int main(int argc, char* argv[])
{
Plus3(1,2);
return 0;
}
四、程序入口
1、程序入口
main是我们代码的入口的方法不是真正程序入口
int main(int argc, char* argv[])
{
return 0;
}
真正的程序入口
五、数据类型
1、C语言数据类型
2、数据类型的三个要素
- 存储数据的宽度
- 存储数据的格式
- 作用范围(作用域)
3、整数类型
char 8BIT 1字节
short 16BIT 2字节
int 32BIT 4字节
long 32BIT 4字节
4、无符号和有符号
#include "stdafx.h"
void Plus()
{
char i = 0xFF;
unsigned char k = 0xFF;
}
int main(int argc, char* argv[])
{
Plus();
return 0;
}
5、浮点类型
1、先将这个实数的绝对值化为二进制格式
2、将这个二进制格式实数的小数点左移或右移n位,直到小数点移动到第一个有效数字的右边。
3、从小数点右边第一位开始数出二十三位数字放入第22到第0位。
4、如果实数是正的,则在第31位放入“0”,否则放入“1”。
5、如果n 是左移得到的,说明指数是正的,第30位放入“1”。如果n是右移得到的或n=0,则第30位放入“0”。
6、如果n是左移得到的,则将n减去1后化为二进制,并在左边加“0”补足七位,放入第29到第23位。如果n是右移得到的或n=0,则将n化为二进制后在左边加“0”补足七位,再各位求反,再放入第29到第23位。
6、ASCII
标准ASCII码只有7位,最高位永远是0(表示128可能的字符,表示常用的字符a-z,A-Z,0-9,逗号等)
扩展ASCII码,最高位永远是1(扩展ASCI码允许将每个字符的第8位用于确定附加的128 个特殊符号字符、外来语字母和图形符号)
#include "stdafx.h"
void Plus()
{
char i = 'A';
}
int main(int argc, char* argv[])
{
Plus();
return 0;
}
7、GB2312
GB2312需要用两个字节来表示,一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到0xF7,,后面一个字节(低字节)从0xA1,到0xFE,这样我们就可以组合出大约7000多个简体汉字了
8、内存图
9、变量
全局变量
-
全局变量在程序编译完成后地址就已经确定下来了,只要程序启动,全局变量就已经存在了,启动后里面是否有值取决于声明时是否给定了初始值,如果没有,默认为0
-
全局变量的值可以被所有函数所修改,里面存储的是最后一次修改的值.
-
全局变量所占内存会一直存在,知道整个进程结束.
-
全局变量的反汇编识别:
MOV 寄存器,byte/word/dword ptr ds:[0x12345678]
局部变量
-
局部变量在程序编译完成后并没有分配固定的地址.
-
在所属的方法没有被调用时,局部变量并不会分配内存地址,只有当所属的程序被调用了,才会在堆栈中分配内存.
-
当局部变量所属的方法执行完毕后,局部变量所占用的内存将变成垃圾数据.局部变量消失.
-
局部变量只能在方法内部使用,函数A无法使用函数B的局部变量.
-
局部变量的反汇编识别:
[ebp-4]
10、分析参数
六、if语句
1、if语句
#include "stdafx.h"
int a;
void test(int x,int y)
{
if(x>y)
{
a = x;
}
}
int main(int argc, char* argv[])
{
test(3,5);
return 0;
}
2、if else语句
// test.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
int g;
void test(int x,int y)
{
if(x>y)
{
g = x + g;
}else
{
g = y + g;
}
}
int main(int argc, char* argv[])
{
test(3,5);
return 0;
}
特点分析
- 如果不跳转,那么会执行到jmp处,jmp直接跳转到END处
- 如果跳转,则会直接跳过jmp END处的代码,直接执行后面的代码
- 第一个jxx跳转的地址前面有一个jmp ,可以判断是if...else...语句
七、基础知识
1、声明和赋值
-
声明变量就是告诉计算机,我要用一块内存,你给我留着,宽度和存储格式有数据类型决定
-
计算机什么时候把这块内存给你,取决于变量的作用范围,如果是全局变量,在程序编译完就已经分配了空间,如果是局部变量,只有所在的程序被调用的时候,才会分配空间
-
全局变量如果不赋初始值,默认是0,但局部变量在使用前一定要赋初值,不然就是CCCCCCCC
2、类型转换
MOVSX 先符号扩展,再传送(结果是FFFF,符号位是什么扩展的部分就是什么,MOVSX用于有符号)
MOV AL,0FF
MOVSX CX,AL
MOV AL,80
MOVSX CX,AL
MOVZX 先零扩展,再传送(结果是00FF,扩展的部分全填0,MOVZX用于无符号)
MOV AL,0FF
MOVZX CX,AL
MOV AL,80
3、表达式
-
表达式无论多么复杂,都只有一个结果
-
只有表达式,可以编译通过,但并不生成代码,需要与赋值或者其他流程控制语句一起组合的时候才有意义.
-
当表达式中存在不同宽度的变量时,结果将转换为宽度最大的那个.
-
当表达式中同时存在有符号和无符号数的时候,表达式的结构将转换为无符号数
4、语句
影响内存或者cpu的就叫语句
5、运算符
- 关系运算符(“==”、“!=”、“>=”、“<=”、“>”、“<”)
- 逻辑运算符(&& || ! )
- 单目运算符(++i)
- 三目运算符( x>y?x:y;)
八、循环语句
1、交换两个数
#include "stdafx.h"
void test()
{
int x = 2;
int y = 1;
x = x + y;
y = x - y;
x = x - y;
printf("%d:%d",x,y);
}
int main(int argc, char* argv[])
{
test();
getchar();
return 0;
}
2、将一个数组倒序输出
#include "stdafx.h"
void test()
{
int arr[5] = {2,4,1,5,3};
int i = 4;
while(i>=0)
{
printf("%d\n",arr[i]);
i--;
}
}
int main(int argc, char* argv[])
{
test();
getchar();
return 0;
}
3、找出数组中最大的值
#include "stdafx.h"
void test()
{
int arr[5] = {2,4,1,5,3};
int r = arr[0];
int i = 1;
while(i<=4)
{
if(arr[i]>r)
{
r = arr[i];
}
i++;
}
printf("%d",r);
}
int main(int argc, char* argv[])
{
test();
getchar();
return 0;
}
4、写一个函数int prime(int x),如果x是素数返回值为1,否则返回0。
#include "stdafx.h"
int test(int x)
{
int i = 2;
while(i
5、俩俩比较数组的值,将最大的一个存储到数组的最后一个位置
#include "stdafx.h"
void test()
{
int arr[5] = {2,5,1,4,3};
int t = 0;
int i = 0;
while(i<4)
{
if(arr[i] > arr[i+1])
{
t = arr[i];
arr[i] = arr[i+1];
arr[i+1] = t;
}
i++;
}
int k = 0;
while(k<5)
{
printf("%d\n",arr[k]);
k++;
}
}
int main(int argc, char* argv[])
{
test();
getchar();
return 0;
}
6、判断数组是否是对称的,如果是返回1,不是返回0.
#include "stdafx.h"
int test()
{
int arr[5] = {1,2,3,2,1};
int t = 4;
int i = 0;
while(i
7、冒泡排序
#include "stdafx.h"
void test()
{
int arr[8] = {61,21,34,18,16,33,58,45};
int lenght = 8;
int i =0;
int k = 0;
while(i arr[k+1])
{
arr[k] = arr[k] + arr[k+1];
arr[k+1] = arr[k] - arr[k+1];
arr[k] = arr[k] - arr[k+1];
}
}
i++;
}
int m = 0;
while(m<8)
{
printf("%d\n",arr[m]);
m++;
}
}
int main(int argc, char* argv[])
{
test();
getchar();
return 0;
}
九、参数传递
1、本机尺寸
-
如果本机是32位的,那么对32位的数据支持最好,如果是64位的,那么对64位的支持最好
-
整数类型的参数,一律使用int类型,char类型或者short类型的参数不但没有节省空间,反而浪费了多余的操作.
-
参数传递的本质:将上层函数的变量,或者表达式的值“复制一份”,传递给下层函数
2、局部变量
-
小于32位的局部变量,空间在分配时,按32位分配.
-
使用时按实际的宽度使用.
-
不要定义char/short类型的局部变量.
-
参数与局部变量没有本质区别,都是局部变量,都在栈中分配.
-
完全可以把参数当初局部变量使用
#include "stdafx.h"
void test()
{
}
int main(int argc, char* argv[])
{
test();
getchar();
return 0;
}
#include "stdafx.h"
void test()
{
char a = 1;
char b = 2;
}
int main(int argc, char* argv[])
{
test();
getchar();
return 0;
}
3、64位宽度存取
#include "stdafx.h"
__int64 Function()
{
__int64 x = 0x1234567890;
return x;
}
int main(int argc, char* argv[])
{
__int64 x = Function();
getchar();
return 0;
}
4、比较char arr[3] 与 char arr[4] 哪个更省空间
char arr[4] :
void test()
{
char arr[4] = {0};
}
int main(int argc, char* argv[])
{
test();
return 0;
}
char arr[3]:
#include "stdafx.h"
void test()
{
char arr[3] = {0};
}
int main(int argc, char* argv[])
{
test();
return 0;
}
5、数组寻址
#include "stdafx.h"
void test()
{
int x = 1;
int y = 2;
int r;
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
r = arr[1];
r = arr[x];
r = arr[x+y];
r = arr[x*2+y];
}
int main(int argc, char* argv[])
{
test();
return 0;
}
6、桶排序
原理:
0 0 0 0 0 0 0
0 1 2 3 4 5 6
1、先定义一个需要排序的数组
int arr[6] = {2,4,6,3,1,2};
2、再定义一个排序的数组(这个数组的长度由arr决定,长度是arr中最大的数是6)
int ret[7] = {0};
3、arr中的值,对应着ret的下标
4、循环arr的值,并且将对应的ret下标的值+1
5、打印
#include "stdafx.h"
void test()
{
int arr[6] = {2,4,6,3,1,2};
int ret[7] = {0};
for(int i = 0 ; i < 6 ; i++)
{
ret[arr[i]] = ret[arr[i]] + 1;
}
int m = 0;
#k<7 是因为,arr最大的数是6,所以需要循环6次
for(int k = 0 ; k < 7 ; k++)
{
if(ret[k] > 0)
{
m = ret[k];
for(;m > 0;m--)
{
printf("%d\n",k);
}
}
}
}
int main(int argc, char* argv[])
{
test();
getchar();
return 0;
}
十、多维数组
1、数组越界
#include "stdafx.h"
void test()
{
int arr[5] = {1,2,3,4,5};
arr[6] = 0x123456;
}
int main(int argc, char* argv[])
{
test();
getchar();
return 0;
}
2、缓冲区溢出
#include "stdafx.h"
void HelloWord()
{
printf("Hello World");
getchar();
}
void Fun()
{
int arr[5] = {1,2,3,4,5};
//HelloWord函数是全局变量,编译器编译后是个固定的值
arr[6] = (int)HelloWord;
}
int main(int argc, char* argv[])
{
Fun();
getchar();
return 0;
}
3、多维数组
-
一维数组与多维数组在反汇编上没有区别
-
数量如果不够,其他的值会补零
-
不允许超过维度的最大长度(不然不能编译)
int arr[3][4] = {
{1,2,3,4,5,6},
{5},
{9}
}
- 可以省略里面的{ }
arr的长度是12,现在只分了10,剩下两个编译器自动帮我们补0
int arr[3][4] = {1,2,3,4,5,6,7,8,9,10};
- 可以省略第一维的值
它会按4个一组分配,多余的补0
int arr[][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
- 二维数组查找
arr[0][8] = arr[0*12 + 8]
arr[1][7] = arr[1*12 + 7]
int arr[5][12] = {
{1,2,1,4,5,6,7,8,9,1,2,3}
{1,2,1,4,5,6,7,8,9,1,2,3}
{1,2,1,4,5,6,7,8,9,1,2,3}
{1,2,1,4,5,6,7,8,9,1,2,3}
{1,2,1,4,5,6,7,8,9,1,2,3}
}