滴水逆向笔记系列-c语言总结6-20.多级指针 数组指针 函数指针-21.位运算-22.内存分配

第二十课 c语言13 多级指针 数组指针 函数指针

1.多级指针反汇编

一二级指针

可以看到p1== *(p1+0) == p1[0]
image.png
image.png
本来一直没想懂为什么是movsx ecx,byte ptr [eax],是byte,才发现p1是char
类型,所以才得用movsx拓展
image.png
(p1+2) == p1[2],指针可以用和[]取值,他们是一样的
(((p3+1)+2)+3) == p3[1][2][3]
image.png
((p2+1)+1)中,(*(p2+1)+1)是char类型,所以他的+1等于+4,(p2+1)是char类型,他的+1就是+1*
image.png

三级指针

p3是char类型,p3是char类型,(p3)是char类型,((p3))是char类型
image.png

总结:

image.png

2.数组指针

基本特性

image.png

数组指针的*p和p

image.png
虽然*px和px的值一样,但是类型不一样,所以运算出来的值也不一样

疑问?

为什么px的值会和px一样?
看一下反汇编
image.png
px这个值的地址存放的就是1,那我
px就是取0113FD38这个地址的值,为什么不是取出1呢
写个普通的例子,他*px是会取出px的值后再取一次
image.png

px的类型是int (*)[2],所以是指向arr数组首地址,*px的类型是int [2],
还是一个数组类型,数组类型的含义还是数组的首地址,所以*px的值还是一个地址,
参考直接arr的时候,arr取值还是一个数组首地址。

总结:

  • 多去使用砍星看看目前的变量是什么类型
  • 运算才需要砍星,px的本身类型是int (*)[2],这个类型的宽度是4,虽然是数组指针但也熟是个指针类型的宽度

数组指针运算

image.png
(px+1)就先把px的类型(int ()[2])砍个星,那么现在砍完是int [2],那么(px+1)等于px+8
((px+3)+3) px+3算完是int [2]类型,要继续+3运算就继续砍星,int [2]砍完是int类型,所以(*(px+3)+3)第二个+3就是+12

总结:

  • 运算记得砍星砍类型宽度
  • ((px+3)+3)相当于px[3][3]

3.多维数组指针

一维数组

通过上面数组指针的运算已经练的很熟练了,+48,指向第49个数据也就是0xA0,但是我们的数据类型是int (*)[5],宽度为4,所以读出来的数据是000000A0
image.png

二维数组

二维数组运算

1220761906.jpg
一样砍星运算,*(px+3)要运算砍星后的类型是char [2][3],所以+3等于+18,((px+3)+2)运算砍星后的类型是char[3],所以+2等于+6

4.函数指针

代码也是数据

基础特征

函数指针宽度还是4,但是由于砍星后宽度不确定,无法进行加减运算,但是可以比较大小1220875178.jpg

赋值

定义的函数指针的返回值和参数,要和在赋值的函数的返回值和参数一样,否则一运行就挂了,可以强制转型编译成功但是一运行还是会挂
1220877199.jpg

函数也是一段数据

论证函数其实也就是一段数据,这段数据的地址被指向函数指针时,可以被当做函数执行,强转欺骗编译器
-1438804603.jpg
1221654710.jpg

作业

1220818511.jpg
1、不完全正确,不要这么理解,指针就是指针,爱指哪就指哪,指针只是操作数据的工具

第二十一课 c语言14 位运算

1.什么是位运算

  • 位运算效率高
  • 计算机的加减乘除所有运算到最后都需要位运算
  • 位运算还可以用于加密

2.汇编中的算数位移指令

  • SAL(Shift Arithmetic Left):算术左移(和SHL效果一样)
  • SAR(Shift Arithmetic Right):算术右移
  • 格式:即算数移位指令后面的第一个操作数是寄存器或者内存;第二个操作数是寄存器或者立即数

image.png

反汇编例子

0x81二进制为10000001,左移一位后为00000010,左边最高位给cf,右边最低位补0
image.png
image.png
如果是用eax寄存器的话最高位的1就直接左移,不用移到cf标志位了
image.png
0x81二进制为10000001,右移一位后为11000000,左边补最高位,右边最低为的移到cf标志位
image.png
image.png

3.逻辑移位指令

  • SHL(Shift Left):逻辑左移
  • SHR(Shift Right):逻辑右移

左移和算数移位指令一样,右移就不一样,最左边补0
image.png

4.循环移位指令

  • ROL(Rotate Left):循环左移
  • ROR(Rotate Right):循环右移

image.png

5.带进位的循环移位指令

  • RCL(Rotate through Carry Left):带进位循环左移
  • RCR(Rotate through Carry Right):带进位循环右移

image.png

作业

1246703167.jpg

第二十二课 c语言15 内存分配

1.c程序的执行步骤

  • 替换 -->编译 -->链接 -->装入内存 --> 执行

2.宏定义

无参数

#define 标识符 字符序列

#define DEBUG 1				
void Function(){		
	//....	
	if(DEBUG)
		printf("测试信息");
}	

有参数

#define 标识符(参数表) 字符序列
类似定义函数,但是define是把标识符直接替换成后面字符序列(函数执行代码),不会在堆栈创建空间

#define MAX(A,B) ((A) > (B)?(A):(B))
void Func(){
	int x = MAX(1,2);
}

注意事项:

  • 宏名标识符与左圆括号之间不允许有空白符,应紧接在一起
  • 宏与函数的区别:函数分配额外的堆栈空间,而宏只是替换
  • 为了避免出错,宏定义中给形参加上括号
  • 末尾不需要分号
  • define可以替代多行的代码,记得后面加\(一行写不下,就加\接着下一行写)
#define MALLOC(n,type)\					
		((type*)malloc((n)*sizeof(type)))

3.动态分配内存

前面学习的int x; char arr[100];都是静态申请内存,今天学学动态的申请内存

malloc函数

作用:分配所指定大小的内存空间,并返回一个指向它的指针,如果内存空间不够,则返回NULL
声明:

#include "stdlib .h"
void* malloc(size_t size)

void*

void*表示任何类型的指针,在需要的时候再去强转成我们需要的对应类型指针,比如malloc动态申请内存,要返回一个指向内存的指针,但是我们声明时不知道要用一个什么类型的指针,那么返回值可以使用void*,因此宽度就不确定,无法做加减运算

使用:

//在堆中申请内存,分配128个int
int* ptr = (int *)malloc(sizeof(int)*128);   //假设这块内存要给一个int型数组使用,将void*强转int*

//无论申请的空间大小,一定要进行校验,判断是否申请成功	
if(ptr == NULL){	
	return 0;
}
//初始化分配的内存空间,将分配的这片内存中全设为0(可以不用加,这里是害怕这块内存中有别人留下的数据)
memset(ptr,0,sizeof(int)*128);
//使用内存
*(ptr) = 1;   //使用指针来操作指向的内存中的数据
//使用完毕,释放申请的堆空间
free(ptr);
//将指针设置为NULL。因为这次我使用了ptr指针,我用完之后ptr应该还是指向了最后的内存中的地址,如果有坏蛋尝试使用了ptr指针,即用完后又使用了ptr指针,那很可能把原先指向的内存中的其他数据给读出来了,不安全。如果设置了NULL,后面不小心使用ptr,会报错
ptr = NULL;

内存泄露问题:

我们平时如果在函数外定义一个变量,分配的内存在全局区;在函数内定义一个变量,分配的内存在堆栈;使用完这个变量,也不用我们手动的去释放分配的内存空间,因为堆栈平衡等原因,使用完后这些内存中的数据就变成了垃圾,下一次再使用赋初始值覆盖这块内存中的数据即可。
但是现在如果我们使用malloc函数动态申请内存,分配的内存空间在堆中,堆有一个特点,如果此时一个数据占用了堆中的某块内存,那么操作系统就会记住这块内存已经分配出去了,其他数据就不能占用了,要么等待释放、要么此exe程序退出后,其他的数据才能再使用这块内存。
但是像服务器上运行的程序,会长时间运行,使用malloc函数申请内存,如果使用完没有释放,就会造成这块内存一直被占用,当数据庞大时,会将堆全部占住,最后内存占用率会很高,程序就会奔溃,这就是内存泄露问题(堆)。所以一定要释放内存

作业

image.pngimage.png

#pragma warning(disable:4996)//忽略函数不安全警告
int F_Size(FILE* fp)
{
	fseek(fp, 0, 2);
	int len = ftell(fp);
	fseek(fp, 0, 0);
	return len;
}

void F_exe()
{
	FILE* fp;
	fp = fopen("C:\\Windows\\notepad.exe","rb");

	char* addr = (char*)malloc(F_Size(fp));

	if (addr)
	{
		fread(addr, F_Size(fp),1,fp);
	}
	printf("%x",addr);
    free(addr);
    fclose(fp);
}

int main()
{
	F_exe();
}

ftell函数和fseek函数

返回当前文件位置指示符,可以和fseek函数联合使用,先使用fseek把文件指针定位到文件尾部,ftell就可以返回当前文件位置距离文件头还有多少字节,从而计算出文件大小

流stream 的文件位置为给定的偏移 offset,参数 offset 意味着从给定的 whence 位置查找的字节数。
  int fseek( FILE *stream, long offset, int origin );
	第一个参数stream为文件 指针
	第二个参数offset为 偏移量 ,正数表示正向偏移,负数表示负向偏移
	第三个参数origin设定从文件的哪里开始偏移,可能取值为:SEEK_CUR、 SEEK_END 或 SEEK_SET
	SEEK_SET: 文件开头
	SEEK_CUR: 当前位置
	SEEK_END: 文件结尾
	其中SEEK_SET,SEEK_CUR和SEEK_END依次为0,1和2.
	简言之:
	fseek(fp,100L,0);把文件内部 指针 移动到离文件开头100字节处;
	fseek(fp,100L,1);把文件内部 指针 移动到离文件当前位置100字节处;
	fseek(fp,-100L,2);把文件内部 指针 退回到离文件结尾100字节处。

posted @ 2024-03-14 15:15  小新07  阅读(41)  评论(0编辑  收藏  举报