Is char Nice ? : 在 C 语言中,有问题的 char 类型不会递增的代码?
你好,我是无能。
因为好奇,我尝试了一下。
https://blog.httrack.com/blog/2014/05/30/c-corner-cases-and-funny-things
首先,我尝试在上述状态下编译并运行。
#include <stdio.h>
/** Return the next character within a \0-terminated string, or EOF. **/
int my_read_char(const char *buffer, size_t *offs) {
if (buffer[*offs] != '\0') {
return buffer[*offs++]; /* here's the trap */
} else {
return EOF;
}
int main() {
const char *buffer = "Hello, World!";
size_t offs = 0;
printf("Reading characters from buffer:\n");
while (1) {
int ch = my_read_char(buffer, &offs);
if (ch == EOF) {
break;
}
printf("Character read: %c, offs: %zu\n", ch, offs);
}
return 0;
}
然后编译并运行
gcc -o bugcode bugcode.c
./bugcode
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Charact^C
alleycat:[haturatu]:~/clang$
啊啊啊!!!
因此很危险,但是我将日志输出到名为 log 的文件中,查看初始执行情况。
话说回来,尽管我急忙执行了 `ps -ef | grep bugcode` 来确认进程 ID 并将其杀死...
alleycat:[haturatu]:~/clang$ ls -la
合计 1256824
drwxr-xr-x 2 haturatu haturatu 4096 6月 14 23:38 .
drwx------ 39 haturatu haturatu 4096 6月 14 23:38 ..
~
-rw-r--r-- 1 haturatu haturatu 1286922240 6月 14 23:39 log
文件大小变得非常大,达到了大约1.2GB...这简直是足以使系统崩溃的危险代码...无法用 view 查看,但可以用 vim 打开。
alleycat:[haturatu]:~/clang$ head log
Reading characters from buffer:
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
从一开始就没有更新(递增)。
我想在 ChatGPT 的帮助下理解这是怎么回事。
EOF 和 \0 的区别
EOF(End Of File)是表示文件结束的特殊值,通常为 -1。而 '\0' 是 C 字符串的终止符,即空字符,其值为 0。
我似乎在这里明白了什么,但我的头脑又开始混乱了。
为了冷静下来,我将询问这段代码在此处发生的流程。它已更改为 my_read_char_with_cast,但请忽略。
流程详情
初始状态:
offs 为 0。
缓冲区没有空终止符(没有 '\0')。
循环开始:
调用 my_read_char_with_cast 函数。
buffer[*offs] 是 'H'(第一个字符),所以不是 '\0'。
返回 'H',offs 变为 1。
循环继续:
下次调用时读取 buffer[1]('e'),并返回。
offs 变为 2。
重复:
此过程继续。
最终 offs 变为 11,读取 buffer[11]('!'),并返回。
offs 变为 12。
无限循环:
buffer[12] 的值未定义(引用缓冲区外的内存区域),且不是 '\0'。
由于 buffer[*offs] 始终不是 '\0',因此会发生无限循环,而不会达到 EOF。
我反而更不明白了。实际测试结果显示从一开始就发生了无限循环,所以 offs 根本没有达到 12。调试时显示
Returning character: H
Character read: H, offs: 0
my_read_char called with offs: 0
Returning character: H
Character read: H, offs: 0
my_read_char called with offs: 0
Returning character: H
Character read: H, o^C
alleycat:[haturatu]:~/clang$
果然,offs 仍然是 0。
那么,让我们回到 HTTrack 的博客。
此函数对于 ASCII > 127 的字符返回负值。特别是,对于 ASCII 255 (0xFF),它返回 -1。这也是 EOF 的值。
原来如此?那么为什么会返回 -1 呢?我问问 AI。
char 类型的特殊性
有符号 8 位整数: C 语言的 char 类型通常被视为有符号的 8 位整数。它可以表示 -128 到 127 的范围。
回绕: 当 char 类型的值达到最大值(127)时,下一个递增操作会使其回绕到最小值(-128)。这是由于 2 的补码表示,127 递增后会变成 -128。
关于 ASCII 255(0xFF)的考察
超出 ASCII 范围: 在 ASCII 表中,值 255(0xFF)超出范围,通常被视为控制字符。
与 EOF 的关系: 在 C 语言中,EOF 是表示文件结束的特殊值,通常被视为 -1。EOF 用于指示文件结束,但在这里它被用作 ASCII 值 255(0xFF)的返回值。
函数行为的含义
此函数对于 ASCII 255(0xFF)返回 -1 的原因如下:
超出范围的值: ASCII 255 超出 char 类型的范围,通常被视为无效值。
EOF 的利用: 此函数使用 EOF(-1)来指示缓冲区的结束。对于 ASCII 255 返回 -1,这是一种隐式约定,表示 EOF。
这种设计使得函数能够检测到缓冲区结束,并通过返回 EOF 来作为调用者停止读取数据的信号。但是,在此函数的上下文中,如果需要对 ASCII 255 进行特定处理,或者希望正确处理 ASCII 255,则需要注意。
原来如此,原来 char 类型本身就无法处理。这源于 ASCII 127 和 ASCII 255 的区别,正因为如此,EOF 才作为 -1 返回,从而导致无限循环。
HTTrack 网站上的修正代码如下。但是,我用 gcc 编译了下面的代码,但仍然发生了无限循环,所以这个也不行。
/** Return the next character within a \0-terminated string, or EOF. **/
int my_read_char(const char *buffer, size_t *offs) {
if (buffer[*offs] != '\0') {
return (unsigned char) buffer[*offs++];
} else {
return EOF;
}
AI 给出了这个。
int my_read_char(const char *buffer, size_t *offs) {
if (buffer[*offs] != '\0') {
return buffer[(*offs)++];
} else {
return EOF;
}
}
结果,即使进行类型转换,似乎也必须明确地将其作为 char 类型进行递增。
我又问了关于它们区别的问题。
区别详情
1. 包含 unsigned char 类型转换的函数
类型转换的原因:
buffer 是 char 数组,buffer[*offs] 返回 char 类型的值。
char 类型通常是有符号的 8 位整数,但这取决于实现(根据编译器或平台,也可能是无符号的)。
如果字符串中的值取负值,在不进行类型转换的情况下,负值在扩展为 int 类型时可能会保留符号。
例如,如果 buffer[*offs] 的值为 -1,不进行类型转换会返回 -1,但进行类型转换会返回 255(0xFF)。
2. 不进行类型转换的函数
处理有符号字符:
如果 buffer[*offs] 可能具有负值,不进行类型转换会直接返回负值。
这在某些情况下可能会导致意外行为。
那么,结果如何呢?
应该使用哪一个?
包含类型转换的函数:
如果将字符数据作为二进制处理,或者希望将其作为无符号值处理,通常会将其转换为 unsigned char。
这在处理非 ASCII 字符(例如,UTF-8 编码的数据等)时尤为重要。
不进行类型转换的函数:
如果只处理 ASCII 字符,或者有意将其作为有符号字符处理,则不进行类型转换可能没有问题。
这确实是一个特殊的例子呢。
对于后者,不进行类型转换时,将其作为 char 类型处理,通过明确地写成 buffer[(*offs)++],使其作为 char 类型的默认无符号处理,这样就能正确地作为 char 类型进行递增了吗?
字符编码的差异是很难可视化的东西,我们身边有很多这样的陷阱呢...哎呀,真有趣!