-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #61 from doyo2024/main
Add: string.md
- Loading branch information
Showing
7 changed files
with
316 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
#include <stdio.h> | ||
#include <string.h> | ||
|
||
void read(char *str) { // 将字符串读入到从str开始的一段内存中 | ||
int i = 0; | ||
char c; | ||
while ((c = getchar()) != '\n') { // 检查读入的字符是否等于约定的字符串结尾字符,这里是'\n' | ||
str[i++] = c; | ||
} | ||
str[i] = '\0'; // 注意字符串总是以'\0'作为结束 | ||
} | ||
|
||
int main() { | ||
char str2[10], str1[10]; | ||
printf("str1=%p\nstr2=%p\n", str1, str2); | ||
puts("Please input the first string:"); | ||
read(str1); | ||
puts("Please input the second string:"); | ||
read(str2); | ||
printf("Content of the second string: %s\n", str2); | ||
strcat(str1, str2); | ||
printf("Result: %s\n", str1); | ||
printf("Content of the second string: %s\n", str2); | ||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
#include <stdio.h> | ||
#include <string.h> | ||
|
||
void read(char *str) { // 将字符串读入到从str开始的一段内存中 | ||
int i = 0; | ||
char c; | ||
while ((c = getchar()) != '\n') { // 检查读入的字符是否等于约定的字符串结尾字符,这里是'\n' | ||
str[i++] = c; | ||
if (i >= 99) { // 检查字符串长度是否超出了可接受的范围 | ||
while ((c = getchar()) != '\n') | ||
; // 空循环,清空输入缓存,防止影响下一次读入 | ||
break; | ||
} | ||
} | ||
str[i] = '\0'; // 注意字符串总是以'\0'作为结束 | ||
} | ||
|
||
int main() { | ||
char str1[100], str2[100]; | ||
puts("Please input the first string:"); | ||
read(str1); | ||
puts("Please input the second string:"); | ||
read(str2); | ||
int ret = strcmp(str1, str2); | ||
if (ret < 0) | ||
puts("Compare Result: str1 < str2"); | ||
else if (ret > 0) | ||
puts("Compare Result: str1 > str2"); | ||
else | ||
puts("Compare Result: str1 = str2"); | ||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
#include <stdio.h> | ||
#include <string.h> | ||
|
||
void read(char *str) { // 将字符串读入到从str开始的一段内存中 | ||
int i = 0; | ||
char c; | ||
while ((c = getchar()) != '\n') { // 检查读入的字符是否等于约定的字符串结尾字符,这里是'\n' | ||
str[i++] = c; | ||
} | ||
str[i] = '\0'; // 注意字符串总是以'\0'作为结束 | ||
} | ||
|
||
int main() { | ||
char str2[10], str1[10]; | ||
printf("str1=%p\nstr2=%p\n", str1, str2); | ||
puts("Please input the first string:"); | ||
read(str1); | ||
puts("Please input the second string:"); | ||
read(str2); | ||
printf("Content of the second string: %s\n", str2); | ||
strcpy(str1, str2); | ||
printf("Result: %s\n", str1); | ||
printf("Content of the second string: %s\n", str2); | ||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
#include <stdio.h> | ||
#include <string.h> | ||
|
||
void read(char *str) { // 将字符串读入到从str开始的一段内存中 | ||
int i = 0; | ||
char c; | ||
while ((c = getchar()) != '\n') { // 检查读入的字符是否等于约定的字符串结尾字符,这里是'\n' | ||
str[i++] = c; | ||
if (i >= 99) { // 检查字符串长度是否超出了可接受的范围 | ||
while ((c = getchar()) != '\n') | ||
; // 空循环,清空输入缓存,防止影响下一次读入 | ||
break; | ||
} | ||
} | ||
str[i] = '\0'; // 注意字符串总是以'\0'作为结束 | ||
} | ||
|
||
int main() { | ||
char str[100]; | ||
puts("Please input a string:"); | ||
read(str); | ||
int len = strlen(str); | ||
printf("The length of str is: %d\n", len); | ||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
#include <stdio.h> | ||
#include <string.h> | ||
|
||
void read(char *str) { // 将字符串读入到从str开始的一段内存中 | ||
int i = 0; | ||
char c; | ||
while ((c = getchar()) != '\n') { // 检查读入的字符是否等于约定的字符串结尾字符,这里是'\n' | ||
str[i++] = c; | ||
} | ||
str[i] = '\0'; // 注意字符串总是以'\0'作为结束 | ||
} | ||
|
||
int main() { | ||
char str2[10], str1[10]; | ||
puts("Please input the first string:"); | ||
read(str1); | ||
puts("Please input the second string:"); | ||
read(str2); | ||
char *res = strstr(str1, str2); | ||
if (res == NULL) { | ||
puts("Not Found."); | ||
} else { | ||
printf("Found at %d.\n", (int)(res - str1)); | ||
} | ||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
# C语言进阶:字符串 | ||
|
||
Author: [doyo](https://github.com/doyo2024) | ||
|
||
## C风格字符串 | ||
|
||
所谓字符串,顾名思义,就是一连串不间断的字符(注意,不间断不意味着不能有空格,事实上空格也是一种特殊的字符)。如果你学过OI,你可能经常使用一种“string”类型的字符串。但是,string是C++中才有的,在C语言中用不了。 | ||
|
||
在C语言中,我们使用的是一种被称之为“C风格字符串”的形式来描述字符串,即使用一个以“`\0`”结尾的`char`类型数组来存放字符串,例如: | ||
|
||
```c | ||
char str[] = "Hello World!\0"; | ||
``` | ||
|
||
`char`类型用于定义字符变量。一个`char`类型变量占用一个字节(即1B,8bit)的内存空间,可以存放一个ASCII字符。这里的“`\0`”是一个字符,前面的“`\`”是一个转义符,表示它后面的一个字符(例如这里的“`0`”)不表示其原本的意思,而是一个特殊含义(例如“`\n`”不表示小写字母n而是换行)。所以,“`\0`”表示的不是一个ASCII码为0x30的字符“0”,而是一个ASCII码为0x00的空字符。所以,我们也可以说“C风格字符串”是以空字符为结尾的字符串。 | ||
|
||
一般情况下,我们也常常会省略字符串末尾的这个“`\0`”,因为在计算机内部存储字符串常量时会自动为其添加“`\0`”,所以,我们更常使用的是下面这种等价的定义方式: | ||
|
||
```c | ||
char str[] = "Hello World!"; | ||
``` | ||
|
||
这个字符串长度为12(10个英文字母+1个空格+1个叹号),但实际需要占用13B的内存空间,因为“`\0`”也要占用1B的字节。 | ||
|
||
### 字符串的读入 | ||
|
||
C语言中,比较常见的有两种方式:`scanf()`函数和`getchar()`函数(我知道有同学会用`cin`,但那是C++才有的东西)。 | ||
|
||
`scanf()`函数的用法与读入整数时类似: | ||
|
||
```c | ||
char str[100]; // 给要读入的字符串留够空间! | ||
scanf("%s", str); | ||
``` | ||
我们使用占位符“`%s`”来表示字符串。此处我们传入的第二个参数`str`已经是一个指针了,所以我们不需要再对它取一次地址。 | ||
使用上述方法时,默认读入的字符串以空格为结尾,毕竟你不可能在终端中输入空字符。但这也导致它在处理字符串读入时可能不够灵活(例如它必须用两个“`%s`”占位符才能读入“Hello World!”),所以,我们有时也会使用`getchar()`函数作为替代,例如: | ||
```c | ||
void read(char *str) { // 将字符串读入到从str开始的一段内存中 | ||
int i = 0; | ||
char c; | ||
while ((c = getchar()) != '\n') { // 检查读入的字符是否等于约定的字符串结尾字符,这里是'\n' | ||
str[i++] = c; | ||
if (i >= 99) { // 检查字符串长度是否超出了可接受的范围 | ||
while ((c = getchar()) != '\n') | ||
; // 空循环,清空输入缓存,防止影响下一次读入 | ||
break; | ||
} | ||
} | ||
str[i] = '\0'; // 注意字符串总是以'\0'作为结束 | ||
} | ||
``` | ||
|
||
上面这种方法要灵活的多,因为它可以以你想要的字符来区分输入中的不同字符串,例如上例中展示的便是以“`\n`”来进行区分。你也可以使用其它字符,但注意,使用的字符必须是你能通过键盘输入的字符。 | ||
|
||
### 字符串的输出 | ||
|
||
字符串的输出要简单的多,使用`printf()`函数即可,程序会在遇到“`\0`”时自动停止输出: | ||
|
||
```c | ||
printf("%s", str); | ||
``` | ||
也可以使用`puts()`函数,这个函数的不同之处在于它会自动在字符串输出结束后进行换行: | ||
```c | ||
puts(str); | ||
``` | ||
|
||
## string.h与常见字符串处理函数 | ||
|
||
常见的字符串处理函数几乎都可以在string.h中找到。 | ||
|
||
### 获取字符串长度 | ||
|
||
字符串最基本的操作之一。 | ||
|
||
函数原型: | ||
|
||
```c | ||
size_t strlen(const char *str) | ||
``` | ||
该函数传入一个参数`str`,为目标字符串;返回一个整数值,表示`str`的长度(不计入字符串结尾的“`\0`”)。 | ||
[点击此处下载示例代码strlen.c。](/posts/2024/2_advanced_C/code/string/strlen.c) | ||
### 比较字符串 | ||
字符串也是可以比较的,`strcmp()`将两个字符串从左至右依次比较ASCII值大小,直至出现不同字符或到达某一字符串结尾(“`\0`”)。 | ||
函数原型: | ||
```c | ||
int strcmp(const char *str1, const char *str2) | ||
``` | ||
|
||
传入的两个参数是我们要比较的两个字符串;返回值是一个有符号整数,满足: | ||
|
||
* `str1 == str2` 时,返回0; | ||
* `str1 < str2` 时,返回一个负数(不一定是-1); | ||
* `str1 > str2` 时,返回一个整数(不一定是1)。 | ||
|
||
[点击此处下载示例代码strcmp.c。](/posts/2024/2_advanced_C/code/string/strcmp.c) | ||
|
||
### 连接字符串 | ||
|
||
连接字符串是指将两个字符串拼接在一起,例如将“Hello ”(注意结尾有空格)和“World!”拼成“Hello World!”。 | ||
|
||
函数原型: | ||
|
||
```c | ||
char *strcat(char *dest, const char *src) | ||
``` | ||
该函数将字符串src拼接在dest字符串的末尾;返回指向拼接得到的字符串的指针(其实就是dest)。 | ||
[点击此处下载示例代码strcat.c。](/posts/2024/2_advanced_C/code/string/strcat.c) | ||
如果你足够仔细和谨慎,你或许会发现这个函数有个致命的漏洞:没有限制字符串的长度。这可能导致拼接后字符串长度超出了为它预设的内存空间大小。 | ||
例如,`dest`字符串为“Hello ”,但我们只为它预留了7B的内存空间(刚好放下这个6个字符和作为结尾的“`\0`”);`src`字符串为“World!”;显然,此时将src拼接到dest之后,`dest`的长度便超出了我们实际允许它使用的内存空间大小。 | ||
上述这类问题就是大名鼎鼎的**缓冲区溢出(buffer overflow)**,缓冲区就是指我们在内存中划定的一块特定大小的区域(你也可以简单地理解成一个数组)。缓冲区溢出可以造成非常严重的危害。借由这一漏洞,攻击者可以非法覆写内存(不要忘记,任何数据在内存都以0和1的形式存在),从而破坏数据乃至改变程序行为。 | ||
在上述实例代码中,我们特意将字符串占用空间声明得很小,并且移除了对读入字符串长度的检查,大家可以尝试通过溢出攻击,通过strcat修改第二个字符串的数据。 | ||
在CTF中,缓冲区溢出问题也是PWN方向重点研究的问题之一,我们会在相关课程中对这类问题作更深入的探讨,敬请期待。 | ||
介于缓冲区溢出问题的存在,我们更推荐采用下面这个函数来进行字符串拼接: | ||
```c | ||
char *strncat(char *dest, const char *src, size_t n) | ||
``` | ||
|
||
这个函数引入了一个新的参数`n`,用来限制要追加的最大字符数。通过合理设置这个参数,可以有效应对缓冲区溢出的问题。 | ||
|
||
### 复制字符串 | ||
|
||
函数原型: | ||
|
||
```c | ||
char *strcpy(char *dest, const char *src) | ||
``` | ||
将`src`处的字符串复制到`dest`处;返回一个指向`dest`的指针。 | ||
[点击此处下载示例代码strcpy.c。](/posts/2024/2_advanced_C/code/string/strcpy.c) | ||
这个函数与前文所述的`strcat()`类似,也存在缓冲区溢出问题,因此,我们更推荐采用下面这个函数来进行字符串复制: | ||
```c | ||
char *strncpy(char *dest, const char *src, size_t n) | ||
``` | ||
|
||
类似地,第三个参数`n`用来限制能复制的最大字符数。 | ||
|
||
### 字符串查找 | ||
|
||
函数原型: | ||
|
||
```c | ||
char *strstr(const char *haystack, const char *needle) | ||
``` | ||
在`haystack`中查找`needle`是否出现,若是,返回指向第一次找到`needle`时的位置的指针;否则,返回空指针。 | ||
[点击此处下载示例代码strstr.c。](/posts/2024/2_advanced_C/code/string/strstr.c) |