Skip to content

Commit

Permalink
Merge pull request #61 from doyo2024/main
Browse files Browse the repository at this point in the history
Add: string.md
  • Loading branch information
Sikesibian authored Oct 27, 2024
2 parents f8eb8fa + ded88ea commit 81b88ac
Show file tree
Hide file tree
Showing 7 changed files with 316 additions and 0 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,16 @@

</div>
</details>

### 第六次课————c风格字符串

<details>
<summary>点击展开</summary>
<div markdown="1">

#### 讲义

[C语言进阶——C风格字符串](https://ucas-ctf.github.io/posts/2024/2_advanced_C/string)

</div>
</details>
25 changes: 25 additions & 0 deletions posts/2024/2_advanced_C/code/string/strcat.c
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;
}
32 changes: 32 additions & 0 deletions posts/2024/2_advanced_C/code/string/strcmp.c
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;
}
25 changes: 25 additions & 0 deletions posts/2024/2_advanced_C/code/string/strcpy.c
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;
}
25 changes: 25 additions & 0 deletions posts/2024/2_advanced_C/code/string/strlen.c
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;
}
26 changes: 26 additions & 0 deletions posts/2024/2_advanced_C/code/string/strstr.c
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;
}
170 changes: 170 additions & 0 deletions posts/2024/2_advanced_C/string.md
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)

0 comments on commit 81b88ac

Please sign in to comment.