在C语言中打印本地字符串

4
我在一道测试题中对这段C代码感到困惑:
#include <stdio.h>

union Data {
    char var1;
    char var2;
    char varArr[10];
};

int main() {
    union Data data;
    data.var1 = 'a';
    data.var2 = 'b';
    data.varArr[0] = 'c';
    char ctr[3];
    ctr[0] = data.var1;
    ctr[1] = data.var2;
    ctr[2] = data.varArr[0];
    printf("Result: %s\n", ctr);
    return 0;
}

我看到的问题问我这段代码会打印什么,显而易见的答案是ccc,但是数组的长度是3,所有的元素都是c,这让我感到困惑(末尾没有\0)。
我想也许有时候它会打印一些垃圾值,而有时候会按预期工作,但是尝试了几次后,我发现它总是按预期工作。
之后,我决定越界访问并检查数组后面的字节,特别是ctr[3]ctr[4]ctr[5],以进一步了解。我注意到这些字节通常是\0,但并不总是。当它们不是\0时,它们通常会取一些小的垃圾值,比如0x010x04(在这种情况下,它们真的是垃圾值吗?我不知道),但是在这些情况下,也会打印出ccc
我不知道为什么会发生这种情况。起初,我以为可能是一些字大小的内存分配导致后面的字节为零,但是看到它们有时候也可能是非零值,让我感到困惑。
之后,我尝试简化代码:
#include <stdio.h>

int main() {
    char ctr[3];
    ctr[0] = 'c';
    ctr[1] = 'c';
    ctr[2] = 'c';
    printf("Result: %s\n", ctr);
    return 0;
}

这段代码打印了cccX(X是一些看起来在不同运行中大小和内容都在变化的垃圾值)。所以,这两段代码在内存上有一些差异,这是由第一段代码中的局部data变量引起的,但我无法弄清楚原因。
为什么会发生这种情况?如何发生的?
我在一台64位机器上使用gcc 13作为编译器,如果有帮助的话。
附注:我有点期望能够以内存中分配的顺序来解释,而不仅仅称之为未定义行为。据我所知,局部变量在内存位置上是相邻的。也许在data的位置写入的内容以某种方式影响了打印ctr的工作方式。但如果未定义行为是我能得到的最终答案,我也想知道为什么。感谢对未定义行为的解释 :)

2
@pnguib -- 创建没有终止NUL ('\0')的字符串并打印它们是未定义行为。有时你会得到你期望的结果,有时你不会。因为它有时候能工作并不是坚持使用它的好理由。 - undefined
1
是的,@gregspears,我在写第二条评论后(后来删除了)才意识到这一点。对不起 :) - undefined
1
@pmg -- 我们没问题。谢谢你站出来道歉(但不是必须的)。我们社区需要更多这样的精神。 - undefined
1
@gregspears 谢谢你的回答 :) 我原本期望能够对内存分配和本地变量之间的位置进行一些分析。我已经在附言中加入了这部分内容。对于我之前没有解释清楚我期望的内容,我感到抱歉。 - undefined
1
@pnguib -- 明白了。好的,我们无法确切知道每个局部变量的内存地址 -- 它们几乎总是在运行时发生变化。虽然你可以使用printf()来检查这些地址。但更重要的是,这里发生了什么:printf在数组中最后一个'c'字符之后继续读取内存 - 它正在寻找我们没有提供的终止NUL。这是"超出数组末尾读取",非常危险。因此,它会找到数组后面的内存中的任何内容,访问它无法合法访问的内容。任何事情都可能发生 - 这是未定义行为。 - undefined
显示剩余9条评论
2个回答

7
是的,未初始化变量的值是不确定的。它们可能是0,但也可能是任何其他对于数据类型有效的值。如果您在使用printf时没有设置宽度限制,并且没有提供以空字符结尾的字符串,那么会导致未定义行为。
在数组越界读取时也会引发未定义行为。
最危险的未定义行为情况之一是当您的程序看起来正常工作时。这可能导致对预期行为的错误假设,并且根据我的经验,这些期望总是在最糟糕的时候落空。

1
争论... "是的,未初始化的变量[具有自动存储期]的值是不确定的。" C18标准-6.7.9初始化(p10) "如果一个具有自动存储期的对象没有被显式初始化,它的值是不确定的。"(话虽如此,我知道你的意思,但我们必须小心,不要让那些正在学习的人只掌握部分时间的断言 :) - undefined

4

%s告诉printf()相应的参数应被视为字符串(一个以0结尾的char序列),而ctr不是,导致了一个意外的(未定义的)结果。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接