我正在尝试在屏幕上打印一些字符。我使用过很多错误的实现。例如:
示例 1:
#include <stdio.h>
int main(int argc, char const *argv[]) {
char hello[6] = {'h', 'e', 'l', 'l', 'o', '\n'};
char bye[5] = {'b', 'y', 'e', '\n', '\0'};
char end[] = "end";
char dot = '.';
char oops[] = {'o', 'o', 'p', 's'};
printf("%s\n", hello);
printf("%s\n", bye);
printf("%s\n", end);
printf("%s\n", dot);
printf("%s\n", oops);
return 0;
}
输出 1:
问题:
为什么在bye
之前打印了点
字符?
点
字符后面的这些垃圾是什么?
示例 2(删除了点
声明/定义,打印):
#include <stdio.h>
int main(int argc, char const *argv[]) {
char hello[6] = {'h', 'e', 'l', 'l', 'o', '\n'};
char bye[5] = {'b', 'y', 'e', '\n', '\0'};
char end[] = "end";
char oops[] = {'o', 'o', 'p', 's'};
printf("%s\n", hello);
printf("%s\n", bye);
printf("%s\n", end);
printf("%s\n", oops);
return 0;
}
输出 2:
问题
垃圾还在那里!我为他们感觉很好,但为什么 end
被打印了两次?
我设法在应该使用终止符的时候正确地使用它,但是为什么我在打印时会出现这种不一致的情况?我来自 Java 背景,我已经感觉很奇怪了!
最佳答案
你得到奇怪的输出的原因是你有未定义的行为。处理字符串的函数(包括使用 %s
格式说明符和 printf
打印它们)期望它们以 null 终止,否则您会得到未定义的行为。如果发生这种情况,则无法保证特定的输出。它可以按预期工作,可以提供奇怪的输出,可以使程序崩溃或执行不同的操作。通常在打印字符串时,它会继续打印,直到出现访问冲突并崩溃,或者直到遇到下一个空字节(这可能会发生得早而不是晚,并且在发生之前,它会打印“垃圾”) .
这也是为什么改变看似无关的事物可以改变 UB 以不同的方式显现。由于示例2中的变化,堆栈看起来不一样,并且当由于UB错误访问时,它会访问不同的东西。示例 1 中打印 hello
时打印的点可能实际上是 char dot = '.';
中的点。
char hello[6] = {'h', 'e', 'l', 'l', 'o', '\n'};
打印这是未定义的行为,因为它不是以 null 结尾的。
char bye[5] = {'b', 'y', 'e', '\n', '\0'};
您可以将其打印为字符串,因为它以 null 结尾。
char end[] = "end";
由于您使用字符串文字进行此操作,因此会自动添加空终止符;你可以打印它。 char end[]
是一个由四个字符组成的数组:'e'
、'n'
、 'd'
和 '\0'
。
char dot = '.';
这是一个字符
,而不是字符串。用 %s
打印它是未定义的行为,您的编译器可能会警告您。改为使用 %c
打印它。
char oops[] = {'o', 'o', 'p', 's'};
与第一个相同,缺少空终止符,因此使用 %s
打印它是未定义的行为。
The rubbish are still there! I am feeling good for them but why the end was printed twice?
编译器通常将所有字符串放在程序的二进制文件中。因此,您的 end
字符串可能会出现在非空终止的 oops
之后。所以在内存中它看起来像oopsend\0
,这就是为什么它可以打印“oopsend”
(或者做一些完全不同的事情,因为它仍然是未定义的行为)。
一般来说,想知道为什么 UB 会这样做并没有什么好处,因为它并不一致(每次你在不同的编译器/机器上运行它时,它可能会做不同的事情,或者直到你向其他人演示它为止)然后程序突然崩溃)。只是将其视为您不应该做的错误事情。
关于c - char 数组中的终止符,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58522758/