C语言学习day18:字符串操作/ANSI编码/宽字节/消息框/软件/游戏编码/逆向分析中的编码

ops/2024/12/17 1:26:21/

今天我们将学习字符串操作,为什么要着重来说这个呢?因为这是为我们之后window开发和api做准备。好的,我们现在正式开始:

字符串

字符串就是一串文字。

比如:"好好学习,天天向上"就是一个字符串

比如:"good good study,day day up"也是字符串

这俩都是字符串,而区别就是一个是英文,一个是中文。

大家都是来学编程的,相信大家一定遇到过有部分软件会出现中文乱码。或者是目录安装中路径不能含有中文的情况,而引起这个问题的原因就是编码问题

在C语言中,字符串实际上是使用空字符\0结尾的一维字符数组。因此,\0 是用于标记字符串的结束。

比如1:char str1={'a','b','c'};

比如2:char str2="abc";

它们之间 比如1 不是字符串。

我们知道双引号中都是字符串而单引号之间是字符。

代码:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include<string.h>//函数,代码的入口函数
int main()
{char str1[] = "abd";char str2[] = "abc";getchar();return 0;
};

接下来我们看看内存:

 

可以发现str1的地址存放的数据为61、62、63 以及00。

说明了什么?

说明字符串的结尾真的有0。

这样也就证实了 \0 是用于标记字符串的结束。

给大家一串代码:

	char* str = "我爱C语言";printf("%s", str); 

请思考,输出的会是地址?还是字符?还是其他的值?

答案会是 字符!

那么这是为什么,明明赋予指针的值应该是地址,而且输出时并没有用 *(解引用),但是输出的还会是地址中的值呢?

很简单:这里设计了三个知识点

1、字符串常量:在C语言中,字符串常量(例如"我爱C语言")是一个以空字符('\0')结尾的字符数组。在我赋值给char* str时,str保存了这个字符数组的地址。

也就是说 我们可以把字符串看成数组,这证明了我们开头所说的理论。

2、printf的处理:当你用printf("%s", str);来打印str时,printf 会开始从str所指向的地址读取字符,直到遇到空字符('\0')。在这个过程中,不需要手动解引用,因为printf函数本身就可以处理指针,按照字符串格式输出。

也就是说:面对字符数组时,我们不用手动解引用printf本身会处理指针。

3、为何不需要解引用:在C语言中,数组名(或字符串的首地址)在许多上下文中可以隐式转换为指针。因此,当你将指针传递给printf时,%s自动处理指针并开始输出从该地址开始的字符。

多字节/ANSI编码

接下来我们看看一段代码:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include<string.h>//函数,代码的入口函数
int main()
{char str1[] = "abc";char str2[] = "abc";char str3[] = "我爱C语言";char* str = "我爱C语言";printf("%s", str1); getchar();return 0;
};

会输出abc,好的这不是重点,重点是我们调式一下,

看看其中str3的内存数值:

我们已经知道,字符串是以0结尾,所以说明

”我爱C语言“占了9个字节!

也就是说

在这个数值中,中文占了2个字节,而字母C占了一个字节,这些一会占2字节,一会占1字节,这就叫多字节!而多字节其实也就是ANSI编码。

ANSI编码表示英文字符时占用1个字节,而中文时占用2-4个字节。

宽字节

除了多字节,我们还有宽字节。

我们先说说宽字节的概念:

使用2个字节(16位)或更多字节来表示一个字符。

也就是说宽字节字符能够表示更多的字符,包括多种国际字符集中的字符,如中文、日文、韩文等。

接下来我们说说语法:

wchar_t ch = L'A';  // 声明一个宽字符变量,值为'A'
wchar_t str[] = L"Hello, 世界";  // 声明宽字符数组并初始化
wchar_t *str = L"Hello, 世界";  // 声明一个指向宽字符字符串的指针
wprintf(L"%ls\n", str);  // 打印宽字符字符串

以上就是宽字节常用的语法。 

接下来我们说说用法:

宽字节字符的主要用途是支持多字节字符集,如Unicode字符集。使用宽字符时,需要使用专门的函数,wprintfwscanf,来处理wchar_t类型的数据。

总结:

  • wchar_t 是C语言中的宽字符类型,用来表示支持多字节字符集的字符。
  • 宽字符用于表示多种语言(如中文、日文等)中的字符,能够存储更多的信息。
  • 使用宽字符时,打印时需要用到wprintf函数,而不是printf
  • 在Windows上,wchar_t通常占用2字节,在Linux等系统上占用4字节。

现在我们了解了宽字节,接下来看看一串代码:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>// 函数,代码的入口函数
int main()
{// 字符串常量char* str1 = "abc"; // 正确:str1应该是字符指针// 宽字符字符串wchar_t str2[] = L"abc";        // 宽字符数组wchar_t str3[] = L"我爱";  // 宽字符数组wchar_t* str = L"我爱C语言";    // 宽字符指针getchar();return 0;
}

我们先不用输出,我们先看看内存数值,看看str1和str2之间的区别:

这是str1的内存数值:

 这是str2的内存数值:

 我们比较一下二者之间的区别,发现str1也就是字符串,它是1字节占位,而str2(宽字节)则是2字节占位,包括最后的\0结尾。

接下来看看str3的内存数值:

没错也是一个中文占了2字节,也就是说

无论是中文还是英文,它都会占用2字节。

接下来我们试试打印宽字节:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>// 函数,代码的入口函数
int main()
{// 字符串常量char* str1 = "abc"; // 正确:str1应该是字符指针// 宽字符字符串wchar_t str2[] = L"abc";        // 宽字符数组wchar_t str3[] = L"我爱";  // 宽字符数组wchar_t* str = L"我爱C语言";    // 宽字符指针wprintf(L"%ls\n",str);getchar();return 0;
}

结果会是什么?

没错乱码:

那我们要怎么办呢?

请看代码:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <wchar.h>  // 需要引入宽字符库
#include <locale.h>  // 需要包含 locale.h 头文件// 函数,代码的入口函数
int main()
{setlocale(LC_ALL, "Chinese_China");// 字符串常量char* str1 = "abc"; // 正确:str1应该是字符指针// 宽字符字符串wchar_t str2[] = L"abc";        // 宽字符数组wchar_t str3[] = L"我爱";  // 宽字符数组wchar_t* str = L"我爱C语言";    // 宽字符指针wprintf(L"%ls\n",str);getchar();return 0;
}

接下来我们的输出结果会是:

为什么呢?因为我们添加了setlocale ()函数:

在 C 语言中,setlocale 用于设置程序的语言环境(locale)。常见的用法是在打印宽字符之前设置语言环境,确保正确处理 Unicode 字符。例如,中文的区域设置通常为 "zh_CN.UTF-8"(Linux/Unix 系统)或 "Chinese_China"(Windows 系统)。

给大家一个网站,微软的语言字符串,这是应用于setlocale()函数的值。

语言字符串 |Microsoft 学习icon-default.png?t=O83Ahttps://learn.microsoft.com/en-us/cpp/c-runtime-library/language-strings?view=msvc-170

接下来我们拓展一下: 

MessageBoxA

我们在以后会经常看到W的出现,也就是宽字节的出现:

比如说:MessageBoxA()消息框函数,它是多字节,A代表:ANSI

想要用到这类函数,我们需要加头文件导入库#include <windows.h>

函数原型:

int MessageBoxA(HWND hWnd,          父窗口句柄,可以为 NULL 表示无父窗口LPCSTR lpText,      消息框的内容(ANSI 字符串)LPCSTR lpCaption,   消息框的标题(ANSI 字符串)UINT uType          消息框的类型(按钮和图标的组合)
);

参数说明

  1. hWnd

    • 指定消息框的父窗口句柄。
    • 如果设置为 NULL,消息框会作为顶层窗口显示。
  2. lpText

    • 消息框中显示的文本内容,必须是 ANSI 编码的字符串。
  3. lpCaption

    • 消息框的标题,必须是 ANSI 编码的字符串。
  4. uType

    • 指定消息框的样式,包括按钮、图标和默认按钮等。
    • 由一组预定义的常量组合而成,例如:
      • 按钮类型:
        • MB_OK:显示一个“确定”按钮。
        • MB_OKCANCEL:显示“确定”和“取消”按钮。
        • MB_YESNO:显示“是”和“否”按钮。
      • 图标类型:
        • MB_ICONINFORMATION:信息图标。
        • MB_ICONERROR:错误图标。
        • MB_ICONWARNING:警告图标。
      • 示例组合:MB_OK | MB_ICONINFORMATION

返回值

  • 返回值是用户点击的按钮对应的常量值,例如:
    • IDOK:用户点击了“确定”。
    • IDCANCEL:用户点击了“取消”。
    • IDYES:用户点击了“是”。
    • IDNO:用户点击了“否”。

 接下来大家看一段代码,来试着理解一下messageBoxA()函数:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <wchar.h>  // 需要引入宽字符库
#include <locale.h>  // 需要包含 locale.h 头文件
#include <windows.h>  // 包含 Windows API 函数定义int main() {// 弹出一个消息框int result = MessageBoxA(NULL,                // 无父窗口"Hello, World!",     // 消息框的内容"Greeting",          // 消息框的标题MB_OK | MB_ICONINFORMATION  // 消息框样式:确定按钮 + 信息图标);// 根据用户的点击进行判断if (result == IDOK) {MessageBoxA(NULL, "You clicked OK!", "Info", MB_OK | MB_ICONINFORMATION);}return 0;
}

 根据上述概念,想想它的输出会是什么?

我先把结果放下方:

MessageBoxW:

与MessageBoxA一样,只不过,MessageBoxW代表的是宽字符的一个字符串

字符串的操作:

strlen计算字符串的长度

返回字符串中字符的数量(不包括结束符 '\0')。

#include <string.h>
#include <stdio.h>int main() {char str[] = "Hello, World!";printf("Length of str: %zu\n", strlen(str));  // 输出:14return 0;
}

strcmp比较两个字符串

返回值:

  • 0:两个字符串相等。
  • 正数:第一个字符串大于第二个字符串。
  • 负数:第一个字符串小于第二个字符串。
#include <string.h>
#include <stdio.h>int main() {char str1[] = "apple";char str2[] = "banana";int result = strcmp(str1, str2);if (result == 0) {printf("Strings are equal.\n");} else if (result > 0) {printf("str1 is greater than str2.\n");} else {printf("str1 is less than str2.\n");}return 0;
}

 strcpy复制字符串

将一个字符串复制到另一个字符串中。

#include <string.h>
#include <stdio.h>int main() {char str1[] = "Hello, World!";char str2[50];  // 必须有足够的空间存放复制的内容strcpy(str2, str1);printf("str2: %s\n", str2);  // 输出:Hello, World!return 0;
}

strcat连接两个字符串

将第二个字符串追加到第一个字符串的末尾。

#include <string.h>
#include <stdio.h>int main() {char str1[50] = "Hello";char str2[] = ", World!";strcat(str1, str2);printf("str1: %s\n", str1);  // 输出:Hello, World!return 0;
}

strchr查找字符

返回字符串中首次出现指定字符的位置,如果没有找到,返回 NULL

#include <string.h>
#include <stdio.h>int main() {char str[] = "Hello, World!";char *ptr = strchr(str, 'o');if (ptr != NULL) {printf("Found 'o' at position: %ld\n", ptr - str);  // 输出:4} else {printf("'o' not found.\n");}return 0;
}

strstr查找子字符串

返回字符串中首次出现指定子字符串的位置,如果没有找到,返回 NULL

#include <string.h>
#include <stdio.h>int main() {char str[] = "Hello, World!";char *ptr = strstr(str, "World");if (ptr != NULL) {printf("Found substring at: %s\n", ptr);  // 输出:World!} else {printf("Substring not found.\n");}return 0;
}

strncpy安全复制指定长度的字符串

strncpy 将源字符串的前 n 个字符复制到目标字符串,且不会越界。如果源字符串长度小于 n,则目标字符串会自动填充空字符。

#include <string.h>
#include <stdio.h>int main() {char str1[] = "Hello, World!";char str2[10];strncpy(str2, str1, 9);str2[9] = '\0';  // 确保字符串以'\0'结尾printf("str2: %s\n", str2);  // 输出:Hello, Woreturn 0;
}

sprintf格式化输出到字符串 

sprintf 可以像 printf 一样格式化数据,但将输出结果存储到字符串中。

#include <stdio.h>int main() {char str[50];int a = 10, b = 20;sprintf(str, "The sum of %d and %d is %d", a, b, a + b);printf("%s\n", str);  // 输出:The sum of 10 and 20 is 30return 0;
}

 memcpy:复制内存块

将源内存块的内容复制到目标内存块,适用于任意类型的数据,不限于字符串。 

#include <string.h>
#include <stdio.h>int main() {char src[] = "Hello, World!";char dest[50];memcpy(dest, src, strlen(src) + 1);  // 包括终止符'\0'printf("dest: %s\n", dest);  // 输出:Hello, World!return 0;
}

 memset:填充内存块

将内存块中的每个字节设置为指定的值,常用于初始化数组。 

#include <string.h>
#include <stdio.h>int main() {char str[50];memset(str, '*', sizeof(str) - 1);  // 填充字符‘*’str[49] = '\0';  // 确保字符串以'\0'结尾printf("str: %s\n", str);  // 输出:**************************************************return 0;
}

 strlwr:返回原字符串的小写形式

 strupr:返回原字符串的小写形式

宽字节字符的操作:

strcpy、wcscpy、_mbscpy | Microsoft Learnicon-default.png?t=O83Ahttps://learn.microsoft.com/zh-cn/cpp/c-runtime-library/reference/strcpy-wcscpy-mbscpy?view=msvc-170

C 标准库提供了一些专门操作宽字符的函数,定义在 <wchar.h> 头文件中。常见的宽字符操作包括:

  • wprintf:用于输出宽字符字符串。
  • wcslen:获取宽字符字符串的长度。
  • wcscpy:复制宽字符字符串。
  • wcscmp:比较两个宽字符字符串。
#include <stdio.h>
#include <wchar.h>
#include <locale.h>int main() {// 设置程序的区域设置为中文,以便正确处理中文字符setlocale(LC_ALL, "zh_CN.UTF-8");// 定义宽字符字符串wchar_t str[] = L"你好,世界";// 输出宽字符字符串wprintf(L"宽字符字符串:%ls\n", str);// 获取字符串的长度wprintf(L"字符串的长度:%zu\n", wcslen(str));// 复制宽字符字符串wchar_t copy[20];wcscpy(copy, str);wprintf(L"复制的字符串:%ls\n", copy);// 比较宽字符字符串int result = wcscmp(str, copy);if (result == 0) {wprintf(L"两个字符串相等\n");} else {wprintf(L"两个字符串不相等\n");}return 0;
}

注意:

  1. 区域设置(locale)wchar_t 字符串的显示与操作通常依赖于系统的区域设置。在 Windows 系统上,你需要设置正确的区域,以确保宽字符能够正确处理。例如,在中文环境下,可以使用 "zh_CN.UTF-8" 来设置中文区域。

  2. 字符集支持:宽字符字符串(如 UTF-16 或 UTF-32 编码)在不同的操作系统和编译器下可能有不同的实现。比如,Windows 通常使用 UTF-16 编码,而一些 Unix-like 系统(如 Linux)可能使用 UTF-32。

  3. 内存管理:宽字符数组或指针需要足够的内存空间来存储宽字符及其终止符。宽字符通常占用 2 或 4 字节,因此需要根据字符数和编码方式来分配适当的内存。

总结

宽字节操作主要使用 wchar_t 类型及其相关函数来进行。你可以使用 wprintfwcscpywcslen 等函数来处理宽字符字符串。同时,确保使用正确的区域设置来支持多字节字符集的显示和处理。

游戏逆向分析中的编码

最后就是我们的逆向分析啦!!

首先要明确一点,这个游戏的语言是什么?

日语、汉语、繁体字他们的编码都是不一样的

所以,分析一个日本游戏,乱码应该如何解码?Shift_JlS
分析一个繁体字游戏,乱码应该如何解码?Big5
分析一个正常的汉字游戏,乱码应该如何解码?GBK
实践1:在线转换utf-8编码转big5编码、Unicode (UTF-8)编码转成繁体中文(Big5)编码、utf-8编码转换big5编码icon-default.png?t=O83Ahttps://www.haomeili.net/HanZi/BianMaZhuanHuan?ToCode=Big5

 

编码过来之后,它的内存存储的值都是不一样的 

实践2:带大家实践一下CE编码转换,与在线转换对比

我们首先打开ce,随便找个软件附加进去

我用的计算器, 进去之后点击内存,随便找个地址,添加进去。

 

将它地址的类型改为字符串类型:

 Codepage是代码页

双击数值,将它的数值改为”你好“

修改完成后再将类型改为字节数组,并将十六进制勾选上(因为它本身就是十六进制)

 更改完成后,我们再看数值

接下来找到在线转换,也就是在这一目录下的在线网址查看,简体中文的’你好‘ 的编码,是否能与这里的数值对上。

OK,很明显能对上。

为什么我们要来证明它是否能对上呢,很简单。

如果计算器真的有一段数值是”你好“,那么我们便可以在CE中搜索数值一栏,将编码输入进去,这样便可以找到相关的地址。举个例子:

 

如果真有”你好“我们来这里搜索数值,数值类型=字节数组。有该数值,我们便能将其搜索出来。

今天的代码就学在这,大家拜拜,我们争取明天见!

如果有疑问可以发在评论下方,我们共同进步哦。


http://www.ppmy.cn/ops/142510.html

相关文章

SpringBoot左脚进门之Maven管理家

一、概念 Maven 是一个项目管理和整合工具。通过对 目录结构和构建生命周期 的标准化&#xff0c; 使开发团队用极少的时间就能够自动完成工程的基础构建配置。 Maven 简化了工程的构建过程&#xff0c;并对其标准化&#xff0c;提高了重用性。 Maven 本地仓库 (Local Reposi…

淘宝详情网页爬虫:技术解析与实战指南

引言 淘宝作为中国最大的电商平台之一&#xff0c;拥有海量的商品数据。对于开发者来说&#xff0c;获取淘宝商品详情接口是一个常见的需求。本文将介绍如何使用Python编写爬虫&#xff0c;获取淘宝商品详情信息&#xff0c;并探讨在实际应用中可能遇到的挑战与解决方案。 环…

Element Plus Table 组件树形渲染实现方法

Element Plus的Table组件通过指定列表数据的children属性&#xff0c;实现树形数据的渲染&#xff1b;同时使用row-key标识唯一的行&#xff0c;依赖排序和子节点数据结构&#xff0c;以实现连动操作。 重要的设置有&#xff1a; 树形渲染配置项&#xff1a; 通过tree-props 配…

nodeJS转换视频格式

系统需要先安装 FFmpeg Download FFmpeg node安装模块 npm install fluent-ffmpeg 使用示例 把 wmv 格式转换 mp4 格式 const ffmpeg require(fluent-ffmpeg) const path require(path)function convertWmvToMp4(inputPath, outputPath) {ffmpeg(inputPath).output(outputP…

React基础学习

React基础 &#x1f4e3; &#x1f4e3; &#x1f4e3; &#x1f4e2;&#x1f4e2;&#x1f4e2; ☀️☀️点开就是缘分认识一下&#xff0c;我是小冷。是一个兴趣驱动自学练习两年半的的Java工程师。 &#x1f4d2; 一位十分喜欢将知识分享出来的Java博主⭐️⭐️⭐️&#x…

Vue3之响应式系统详解

Vue3中的响应式系统是其核心功能之一&#xff0c;它使得数据变化能够自动触发视图更新&#xff0c;从而简化了开发过程&#xff0c;提高了开发效率。本文将详细阐述Vue3中的响应式系统&#xff0c;包括其核心概念、工作原理、实现方式、应用场景以及优势。同时&#xff0c;本文…

web自动化测试框架playwright

一、背景&#xff1a;UI自动化的痛点&#xff1a; 1、设计脚本耗时&#xff1a; 需要思考要如何模拟用户的操作&#xff0c;如何触发页面的事件&#xff0c;还要思考如何设计脚本&#xff0c;定位和操作要交互的元素、路径、位置&#xff0c;再编写代码逻辑&#xff0c;往复循…

庆祝 2024 年的开源:热门项目和里程碑

随着 2024 年接近尾声&#xff0c;开源社区有很多值得庆祝的事情。今年展示了集体创新的巨大潜力&#xff0c;各行各业都涌现了开创性项目。从 AI 和可持续性到软件开发和创意工具&#xff0c;开源再次证明了其变革的力量。让我们回顾一下今年的主要亮点以及定义 2024 年开源的…