这一节课b站缺失了,参考一下大佬的笔记学习,链接:滴水三期:day21.1-字符数组与字符串_如果一个值溢出某个变量的数据类型存储范围,但仍然存入该变量,那么存入该变量中的-CSDN博客
char arr[10] = {'A','B','C','D','E','F'};? ? //编译器默认在结尾添加0x00
char arr[3] = {'A','B','C'};? ? ? ? ? ? ? ? ? ? //但是如果没有留出'\0'的空间,编译器就不会自动添加
char arr2[6] = {'A','B','C','D','E',0};? ? ?//注意区分这里的0不是'0'
char buffer[100] = "";? ? ? ? ? ? ? ? ? ? ? ? //定义了一个空字符串,字符串中每个字节默认初始为0x00
char buffer[] = ""; ?????????????????????????????//这个数组长度只有1,且默认初始为0x00
定义字符数组来表示字符串时,一定要在结尾给'\0',我们可以手动添加,也可以不添加让编译器自己自动帮我们在结尾处添加'\0',但是一定要注意如果让编译器添加的话,要把字符数组的宽度的长度设置好,一定要预留空间给'\0'
也是字符数组的一种,字符串就会经常在常量区
char names[] = "ABCDE";
//可以省略数组大小,但是此时的数组大小应该6,因为编译器会自动在末尾加0x00
char* p = "ABCDE";
//这个是将常量区中存储ABCDE字符串的首地址赋给p,此时p的长度是4字节,常量区的字符
char arr1[6] = {'A','B','C','D','E','\0'};
char arr2[6] = {'A','B','C','D','E',0}; //注意区分这里的0不是'0'
char arr3[6] = {'A','B','C','D','E'}; //只要比5大,数组的长度随便
char names[] = "ABCDE";
printf("%s\n",arr1);
printf("%s\n",arr2);
printf("%s",names);
char word[8];
scanf("%7s", word);//最多读7个字符
前面有一张内存图,现在来说说之前没讲到的全局变量和常量区
这是海哥上课的例子代码
char* x = "china";
//x中存的就是存储在常量区的china字符串的首地址,x指针型变量直接指向常量区中的存储china字符串的首地址
char y[] = "china";
//这里也是常量区中的china字符串,但是与指针不同的是,这里会将字符串值复制一份到给y字符数组变量分配的内存中(栈)
void Func(){
y[1] = 'A'; //可以修改
*(x + 1) = 'A'; //无法修改
x = "ABC"; //可以修改,相当于重新指向ABC这个新的地址
}
先从反汇编层面看看
可以看到x变量存储的直接就是china常量区的地址,而y存储的是china拷贝过来的值而且放到堆栈里,所以x无法修改但是y数组可以
错误示范
我们要用int类型搜索内容,一次搜四个字节,但是int类型+1后下一次搜索就是每隔四个字节搜一次,而我们现在需要用int类型每隔一个字节搜一次,所以出现下面的错误代码
#include "stdafx.h"
char data[100] = { //全局变量
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x07,0x09,
0x00,0x20,0x10,0x03,0x03,0x0C,0x00,0x00,0x44,0x00,
0x00,0x33,0x00,0x47,0x0C,0x0E,0x00,0x0D,0x00,0x11,
0x00,0x00,0x00,0x02,0x64,0x00,0x00,0x00,0xAA,0x00,
0x00,0x00,0x64,0x10,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x02,0x00,0x74,0x0F,0x41,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x0A,0x00,
0x00,0x02,0x74,0x0F,0x41,0x00,0x06,0x08,0x00,0x00,
0x00,0x00,0x00,0x64,0x00,0x0F,0x00,0x00,0x0D,0x00,
0x00,0x00,0x23,0x00,0x00,0x64,0x00,0x00,0x64,0x00
};
int length = sizeof(data) / sizeof(data[0]);
void find_blood(int num,int width){ //输入要查找的数值,查找宽度
if(width == 1){
for(int i = 0;i < length - width + 1;i++){
if(*(data + i) == num)
printf("add:%x num:%d\n",data + i,*(data + i));
}
}else if(width == 2){
for(int i = 0;i < length - width + 1;i++){
short* p = (short*)(data + i); //直接使用char* data++,但是每次判断前把当前地址转型赋给一个新short* p即可,不需要上面那么麻烦
if(*p == num){
printf("add:%x num:%d\n",p,*p);
}
}
}else if(width == 4){
for(int i = 0;i < length - width + 1;i++){
int* p = (int*)(data + i);
if(*p == num){
printf("add:%x num:%d\n",p,*p);
}
}
}else{
printf("宽度不符合规定");
}
}
int main(int argc, char* argv[])
{
printf("%x\n",data);
find_blood(256,2);
getchar();
return 0;
}
这把借鉴作业了
还有个改进前的思路也可以看看
void findblood(int size){
if(size == 2){ //数值类型为short时,即2字节
short* temp = (short*)blood; //因为每次定位到一个地址,要查的数是从这往后2字节,所以先强转
for(int i = 0;i < 100 - size + 1;i++){
if(*(temp) == 100){ //因为temp是short*类型,所以取的地址宽度为2字节
printf("%x\t%d\n",temp,*temp);
}
char* temp2 = (char*)temp; //因为要一个一个地址挨着找,所以先转换成char*类型,如果拿 short*的temp直接++,那么结果就是源temp中地址+2*1,就不是依次逐个地址找了,就是跳了一个
temp2++; //那么char*变量在++时,结果为:地址 + char宽度,即下一个地址
temp = (short*)temp2;//然后再将下一个地址的值赋给temp,下次比较就是从temp往后数2字节是否 是0x0064
}
}else if(size == 4){ //数值类型为int时,即4字节
int* temp = (int*)blood;
for(int i = 0;i < 100 - size + 1;i++){ //因为如果4字节为单位,则查到倒数第4个地址就是最后 一个地址了,就不用继续我往后查了
if(*(temp) == 100){
printf("%x\t%d\n",temp,*temp);
}
char* temp2 = (char*)temp;
temp2++;
temp = (int*)temp2;
}
1、
2、
3、
注意:x数组需要给大一点的空间,否则在y赋值过去后,没有足够的空间来容纳两个字符串的拼接结果,可能会导致超出数组界限的访问,进而引发未定义的行为。
打印调试出错误
虽然我感觉我理解错题意了下面代码,不过就这样吧
char datas[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x07, 0x09,
0x00, 0x20, 0x10, 0x03, 0x03, 0x0C, 0x00, 0x00, 0x44, 0x00,
0x00, 0x33, 0x00, 0x47, 0x0C, 0x0E, 0x00, 0x0D, 0x00, 0x11,
0x00, 0x00, 0x00, 0x02, 0x64, 0x00, 0x00, 0x00, 0xAA, 0x00,
0x00, 0x00, 0x64, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0x00, 0x74, 0x0F, 0x41, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x0A, 0x00,
0x00, 0x02, 0x57, 0x4F, 0x57, 0x00, 0x06, 0x08, 0x00, 0x00,
0x00, 0x00, 0x00, 0x64, 0x00, 0x0F, 0x00, 0x00, 0x0D, 0x00,
0x00, 0x00, 0x23, 0x00, 0x00, 0x64, 0x00, 0x00, 0x64, 0x00 };
int length = sizeof(datas) / sizeof(datas[0]);
void FindNameAddr(char* pData, char* pName,int width)
{
if (width == 1)
{
for (int i = 0; i < length - width + 1; i++)
{
if (*(pData+i) == *pName)
{
if(*(pData + i +1) == *(pName+1))
if (*(pData + i + 2) == *(pName + 2))
printf("addr:%x\n",pData+i);
}
}
}
else if (width == 2)
{
for (int i = 0; i < length - width + 1; i++)
{
short* ret = (short*)(pData+i);
if (*ret == *((short*)pName))
{
if(*(ret+1) == *((short*)pName+1))
printf("addr:%x\n", pData + i);
}
}
}
else if (width == 4)
{
for (int i = 0; i < length - width + 1; i++)
{
int* ret = (int*)(pData + i);
if (*ret == *((int*)pName))
{
printf("addr:%x\n", pData + i);
}
}
}
else
{
printf("宽度输入错误");
}
}
int main()
{
FindNameAddr(datas, (char*)"WOW", 4);
}
把"if""for"这些常量的地址放入数组
思维不被限制,结构体指针指向的地址不是结构体也可以,只是他的内存宽度对应不上,但是强转后编译器能编译通过,不管指向的地址后面是什么都打印出来就完事了
问题
1、答
问题
答?
结构体指针和结构体的取值也不一样,ret->id和p.id
可以看到*p1== *(p1+0) == p1[0]
本来一直没想懂为什么是movsx ecx,byte ptr [eax],是byte,才发现p1是char*类型,所以才得用movsx拓展
*(p1+2) == p1[2],指针可以用*和[]取值,他们是一样的
*(*(*(p3+1)+2)+3) == p3[1][2][3]
在*(*(p2+1)+1)中,(*(p2+1)+1)是char**类型,所以他的+1等于+4,*(p2+1)是char*类型,他的+1就是+1
p3是char***类型,*p3是char**类型,*(*p3)是char*类型,*(*(*p3))是char类型
虽然*px和px的值一样,但是类型不一样,所以运算出来的值也不一样
为什么px的值会和*px一样?
看一下反汇编
px这个值的地址存放的就是1,那我*px就是取0113FD38这个地址的值,为什么不是取出1呢
写个普通的例子,他*px是会取出px的值后再取一次
px的类型是int (*)[2],所以是指向arr数组首地址,*px的类型是int [2],还是一个数组类型,数组类型的含义还是数组的首地址,所以*px的值还是一个地址,参考直接arr的时候,arr取值还是一个数组首地址。
多去使用砍星看看目前的变量是什么类型
运算才需要砍星,px的本身类型是int (*)[2],这个类型的宽度是4,虽然是数组指针但也熟是个指针类型的宽度
*(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
通过上面数组指针的运算已经练的很熟练了,+48,指向第49个数据也就是0xA0,但是我们的数据类型是int (*)[5],宽度为4,所以读出来的数据是000000A0
一样砍星运算,*(px+3)要运算砍星后的类型是char [2][3],所以+3等于+18,*(*(px+3)+2)运算砍星后的类型是char[3],所以+2等于+6
代码也是数据
函数指针宽度还是4,但是由于砍星后宽度不确定,无法进行加减运算,但是可以比较大小
定义的函数指针的返回值和参数,要和在赋值的函数的返回值和参数一样,否则一运行就挂了,可以强制转型编译成功但是一运行还是会挂
论证函数其实也就是一段数据,这段数据的地址被指向函数指针时,可以被当做函数执行,强转欺骗编译器
1、不完全正确,不要这么理解,指针就是指针,爱指哪就指哪,指针只是操作数据的工具
0x81二进制为10000001,左移一位后为00000010,左边最高位给cf,右边最低位补0
如果是用eax寄存器的话最高位的1就直接左移,不用移到cf标志位了
0x81二进制为10000001,右移一位后为11000000,左边补最高位,右边最低为的移到cf标志位
左移和算数移位指令一样,右移就不一样,最左边补0
#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 MALLOC(n,type)\
((type*)malloc((n)*sizeof(type)))
前面学习的int x; char arr[100];都是静态申请内存,今天学学动态的申请内存
作用:分配所指定大小的内存空间,并返回一个指向它的指针,如果内存空间不够,则返回NULL
声明:
#include "stdlib .h"
void* malloc(size_t size)
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函数申请内存,如果使用完没有释放,就会造成这块内存一直被占用,当数据庞大时,会将堆全部占住,最后内存占用率会很高,程序就会奔溃,这就是内存泄露问题(堆)。所以一定要释放内存
#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();
}
返回当前文件位置指示符,可以和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字节处。