何意味呀,b站视频不全去官网花1块钱买了课程,结果播放不了,无所谓了,根据课件和别的师傅的博客也不是不能看

字符数组与字符串

字符串:

1
2
char name[] = "ABCDE";  //定义时可以省略数组大小,编译器会自己补上0x00
char* p = "ABCDE"; //将常量区中存储ABCDE字符串的首地址赋给p,p的长度是4字节(32位环境下存放地址所需空间,64位那就是8字节)

编译器会在最后面添加一个 00 作为字符串结束的标记
指针定义的字符串存在常量区,数组定义的会存在全局区(全局变量)/栈(局部变量)上

字符数组:

1
2
3
4
5
char arr[6] = {'A','B','C','D','E','\0'};  //正儿八经的定义,结尾也可以直接写0 
char arr[10] = {'A','B','C','D','E','F'}; //不写0,编译器自己会把剩余的部分都填上0
char arr[3] = {'A','B','C'}; //但是如果没有留出'\0'的空间,编译器就不会加,不可以拿来当字符串用
char buffer[100] = ""; //定义一个空字符串,字符串中每个字节默认初始为0x00
char buffer[] = ""; //这个数组长度只有1,且默认初始为0x00

在使用字符数组来表示字符串的时候,要在结尾加上一个 ‘\0’ ,也可以不手动写让编译器自己加,但是一定要给这个空字符预留出位置,不留的话它就不知道该到哪里停,会导致缓冲区溢出等问题。当然如果字符数组只是用来存 char 类型数据的,那就无所谓了。

%s 的用法:打印一个字符串,直到内存为 0 为止

常量区

内存分布图:

哪个可以修改?哪个不能改?

1
2
3
4
5
6
char* x = "china";
char y[] = "china";
void Function() {
*(x+1) = 'A';
y[1] = 'A';
}

编译都可以过,但是上面的不能改,下面的能改,为什么?
char* x = "china"; 不允许改,因为指针定义的字符串存在常量区,常量区不允许修改;
char y[] = "china"; 可以改,因为数组定义的字符串有一份在全局变量区的副本,修改也是修改的这个位置的副本。

哪个可以修改?哪个不能改?

1
2
3
4
5
6
void Function() {
char* x = "china";
char y[] = "china";
*(x+1) = 'A';
y[1] = 'A';
}

先看第一句:

1
2
3
4
void Function() {
char* x = "china";
//char y[] = "china";
}

我们知道默认开辟的栈空间是 40h,加上这个后是 44h,也就是额外给 char* 类型的 x 开了一个位置,而这个位置存储的就是 china 字符串的地址,至于这个字符串自己本身,是存在常量区的

再看第二句:

1
2
3
4
void Function() {
//char* x = "china";
char y[] = "china";
}

发现 eax 里存的也是 china 这个字符串的首地址,演示第一句时那个首地址与这个一样,都是 0042201C,说明第二句这个定义方式也是存到了常量区,但是为什么这种写法就能改直接用指针指却改不了呢?看一下汇编

可以看到它是先把 chin 四个放到 eax 里,然后存到 [ebp-8] 上,还剩一个 a,又用了个 cx,存到 [ebp-4] 上。相比上一种,多了一步常量区字符串的拷贝。

我的理解是,字符串通常是存在常量区的,只是使用数组字符的时候,会把常量区里的字符串拷贝到数据段(全局变量)/堆栈(局部变量)这些可以修改的地方,修改时也是直接修改拷贝的这份副本,常量区原本的那份也是不会变的。

常见字符串操作

  • 返回字符串的长度
    int strlen (char* s) 返回值是字符串 s 的长度,不包括结束符 ‘\0’

    1
    2
    3
    4
    5
    6
    7
    8
    int strlen(char* s) {
    int ret = 0;
    while (*s != 0) {
    ret++;
    s++;
    }
    return ret;
    }
  • 复制字符串
    char* strcpy (char* dest, char* src) 复制字符串 src 到 dest 中,返回指针为 dest 的值

    1
    2
    3
    4
    5
    6
    7
    //c h i n a 0
    //x y z 0
    char* strcpy(char* dest, char* src) {
    char* ret = dest;
    while((*dest++) = (*src++));
    return ret; //dest++了一轮后指向了最后一个位置,所以要额外用一个ret来代表开头的位置
    }

    这样的操作会把 src 字符串里的 ‘\0’ 结束符也复制过去,因为 while 语句里面是先赋值再判断的,所以在遇到 0 停止之前,0 就已经赋值过去了

  • 字符串的拼接
    char* strcat (char* dest, char* src) 将字符串 src 添加到 dest 尾部,返回指针为 dest 的值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //c h i n a 0
    //x y z 0
    //c h i n a x y z 0
    char* strcat(char* dest, char* src) {
    char* ret = dest;
    while (*dest != 0) dest++; //注意不要写成*dest++!=0,因为++会导致后移一位,之前0的位置接不上,就会导致后续遇到0就停了拼接了个寂寞
    while ((*dest++) = (*src++));
    return ret;
    }
  • 字符串比较
    int strcmp (char* s1, char* s2) 一样返回 0,不一样返回 1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //c h i n a 0
    //c h i n a x y z 0
    int strcmp(char* s1, char* s2) {
    while (*s1 && *s2 && (*s1 == *s2)) {
    s1++;
    s2++;
    }
    if (*s1 == 0 && *s2 == 0) return 0;
    return 1;
    }

课后作业

模拟实现CE的数据搜索功能:这一堆数据中存储了角色的名字信息(WOW),请列出角色名的内存地址

1
2
3
4
5
6
7
8
9
10
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
  1. 编写函数,返回角色名字信息的地址,如果没有返回 0
    char* FindRoleNameAddr(char* pData, char* pRoleName)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    int strcmp(char* s1, char* s2) {
    while (*s2 && (*s1 == *s2)) {
    s1++;
    s2++;
    }
    if (*s2 == 0 && *s1 == 0) return 0; //要判断==0是因为可能会存在WOWX也判断为WOW的情况,所以s1也得是个\0才可以
    return 1;
    }

    char* FindRoleNameAddr(char* pData, char* pRoleName){
    if(!strcmp(pData, pRoleName)) return pData;
    return 0;
    }
  2. 编写函数,遍历上面数据中所有角色名字

    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
    #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,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 strlen(char* s) {
    int ret = 0;
    while (*s != 0) {
    ret++;
    s++;
    }
    return ret;
    }

    int strcmp(char* s1, char* s2) {
    while (*s2 && (*s1 == *s2)) {
    s1++;
    s2++;
    }
    if (*s2 == 0 && *s1 == 0) return 0;
    return 1;
    }

    char* FindRoleNameAddr(char* pData, char* pRoleName){
    if(!strcmp(pData, pRoleName)) return pData;
    return 0;
    }

    int main(int argc, char* argv[]) {
    char* name = "WOW";
    int len = strlen(name);
    for (int i = 0; i < 100 - len; i++) { //因为结尾也要配上\0,所以多检查一个字节
    char* addr = FindRoleNameAddr(data + i, name);
    if (addr != 0) {
    printf("%p\n", addr);
    }
    }
    return 0;
    }