printf为什么要打印一个以非null结尾的字符串



C编程手册说,我的字符串必须以null结尾才能使用printf打印,但下面的程序仍然打印字符串,尽管它不是以null结尾!

#include <stdio.h>
#include <stdlib.h>
int main() {
int i;
char str[10];
for(i = 0; i < 10; i++) {
str[i] = (char)(i+97);
}
printf("%s", str);
}

我使用的是Code::Blocks IDE。

读取超出数组边界是未定义的行为。事实上,你很不走运它没有崩溃。如果您运行它足够多次或在函数中调用它,它可能会(也可能不会)崩溃。

您应该始终终止字符串,或者使用宽度说明符:

printf("%.10s", str);

str的第10个元素之后的内容恰好为null。该null超出了数组的定义边界,但C没有数组边界检查。在你的情况下,这只是运气的结果。

根据C标准,printf函数打印字符串中的字符,直到找到空字符。否则,在定义了数组索引之后,将不定义它要做什么。

我已经测试了你的代码。并且在打印";CCD_ 2";,它打印一些垃圾值。

如果在调用之前执行其他操作,则堆栈区域将包含未使用的数据之外的其他数据。想象一下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int use_stack(void) {
char str[500];
memset(str, 'X', sizeof(str));
printf("Filled memory from %p to %p.n", &str, &str + sizeof str);
}
void print_stuff() {
int i;
char str[16]; // Changed that so that 10..15 contain X
for(i = 0; i < 10; i++) {
str[i] = (char)(i+97);
}
printf("%s<END>", str); // Have a line break before <END>? Then it comes from i.
printf("&str: %pn", &str);
printf("&i: %pn", &i);
// Here you see that i follows str as &i is &str + 16 (0x10 in hex)
}
int main() {
use_stack();
print_stuff();
}

您的堆栈区域将充满Xes,而printf()将看到它们。

在你的情况和你的环境中,堆栈是巧合的"干净";在程序启动时。

这种情况可能会发生,也可能不会发生。如果编译器将变量i直接放在数组之后,那么您的数据仍将以NUL终止,因为第一个字节是i的值(您碰巧也打印了它——在您的情况下,它可能是一个libne中断——而第二个字节是一个NUL字节。即使是这种情况,您的代码也会调用UB(未定义行为)。

如果您的输出包含0A字符,您能看看(通过将程序输出管道传输到hexdump或类似的文件中)吗?如果是的话,我的猜测是正确的。我刚刚测试了它,在我的编译器(GCC)上似乎就是这样。

如前所述,你不应该依赖任何东西。如果你在<END>之前看到断线,我的猜测是对的。如果你看看现在打印的指针,你可以比较它们在内存中的地址。

因为在调试模式下,*(str+10)和整个未使用的空间都有一个初始化值"0",所以看起来它是0终止的。

clang -O0 t.c -o t # Compile in debug mode
./t

输出:

abcdefghij

和:

clang -O2 t.c -o t # Compile with optimization
./t

输出:

abcdefghij2÷d=

最新更新