-4

由于 C 字符数组需要空终止符,因此以下代码会打印四个a和一些垃圾字符。

char y[4] = {'a', 'a', 'a', 'a'};
printf("y = %s\n", y);

输出:

y = aaaa�

但是,以下代码不会产生乱码。

char y[4] = {'a', 'a', 'a', 'a'};
char z[4] = {'b', 'b', 'b'};

printf("y = %s\n", y); 
printf("z = %s\n", z);

输出:

y = aaaa
z = bbb

我知道第四个字符z是用空终止符自动初始化的。我也猜想yz在内存中彼此相邻分配。

但是在这种情况下,C 如何正确打印 4 a而不是前者呢?它是否确定下一个字节已经分配给另一个变量,所以它应该停止打印?

4

3 回答 3

5

printf("y = %s\n", y);在这种情况下,是未定义的行为。换句话说,你很幸运——也许你在命中 NUL 之前打印了一些不可见的字符,也许由于堆栈对齐,数组后面有一个零,也许星星是正确的。


我通常不会详细说明“这是 UB,请勿触摸”,但我感到奇怪的是被迫这样做。

看,这是你的程序:

#include <stdio.h>
int main() {
    char y[4] = {'a', 'a', 'a', 'a'};
    char z[4] = {'b', 'b', 'b'};

    printf("y = %s\n", y); 
    printf("z = %s\n", z);
}

现在我将使用我的特殊编译器标志来编译它:

$ cc -O3 so15727258.c -o so15727258 -fstack-protector-all -Wall

并运行它:

$ ./so15727258
y = aaaa?>A??0F
z = bbb

哎呀,哈哈,这完全是垃圾。更好的是,由于堆栈保护器,它是随机垃圾,所以它甚至(简单)不是确定性的。哇!


还是不服气?特殊的编译器标志对你来说太奇怪了吗?尝试

#include <stdio.h>

int bar() {
    char x[4096];
}

int foo() {
    char y[4] = {'a', 'a', 'a', 'a'};
    char z[4] = {'b', 'b', 'b'};
    printf("y = %s\n", y); 
    printf("z = %s\n", z);
}

int main() {
    bar();
    foo();
}

编译它:

$ cc so15727258.c -o so15727258 -Wall

并运行它:

$ ./so15727258
y = aaaa?????ic?
z = bbb

还是完全垃圾!请记住——这些例子都是说明性的。当然这是未定义的行为,所以你可能会得到一些完全不同的结果,然后回到这里告诉我“但是 XYZ 有效”。这就是未定义行为的定义。

于 2013-03-31T07:11:39.510 回答
2

你的程序表现出未定义的行为,即使它看起来可以工作。'\0'对于大多数字符串操作和打印,字符串必须以 终止。

如果您想知道它是如何在某些情况下仍然有效的,那么对象之间(或相邻对象中)可能有零字节,它们可能充当字符串终止符。

于 2013-03-31T07:13:12.713 回答
0

你不是很幸运......你可以测试它。打印数组后面字节的内存内容。我可以闭着眼睛向你保证,它是零……

于 2013-03-31T07:17:33.763 回答