为什么这个非空结尾字符串被正确打印?

6
昨天我有一次单元测试。其中一个程序是复制字符串并在没有字符串函数的情况下找到其长度。这是我编写的代码:
#include <stdio.h>

int main(){
    char str1[100], str2[100] = {'a'};

    printf("Enter a string\n");
    fgets(str1, sizeof(str1), stdin);

    int i;
    for(i = 0; str1[i] != '\0'; i++){
        str2[i] = str1[i];
    }   

    str2[i] = '\0';

    printf("Copied string = %s", str2);

    printf("Length of string = %d", i-1);
}

我有一个相当惊讶的观察到!即使注释掉了str2[i] = '\0',该字符串也会被正确地打印,即在初始化中不应被覆盖的额外的'a'

在注释掉str2[i] = '\0'后,我期望看到这个输出:

test
Copied string = testaaaaaaaaaaaaaaaaaaaaaaaaaaa....
Length of string = 4

以下是输出结果:

test
Copied string = test
Length of string = 4
< p>如何正确打印str2?编译器是否识别了字符串的复制并自动添加了空终止符?我使用的是gcc,但clang也产生了类似的输出。< /p>
2个回答

8
str2[100] = {'a'};并不会用100个重复的a填充str2。它只是将str[0]设置为'a',其余元素设置为零。
早在C89版本中就规定了:

3.5.7初始化

...

语义

...

如果具有静态存储期的对象没有被显式初始化,则会隐式地初始化每个算术类型成员为0,每个指针类型成员为null指针常量。如果具有自动存储期的对象没有被显式初始化,则其值是不确定的。

...

如果列表中的初始值少于聚合体的成员数,则聚合体的其余部分将隐式地与具有静态存储期的对象相同。


哦,所以其他的被设为0,也就是'\0'的意思?因此它有效吗? - Hemil
@ Hemil 是的,确切地说。 - Mark Tolonen
1
@virolino 不是的。数组已经初始化了。 - Shawn
1
@virolino 不会的,它们肯定是0。请看我的回答以了解原因。 - Sourav Ghosh
嗯...我不知道那个规格。谢谢大家。 - virolino
显示剩余4条评论

3

首先,聚合类型的初始化规则[1]引用了C11的第6.7.9章节(我强调)。

初始化顺序应按照初始化器列表的顺序进行,为特定子对象提供的每个初始化器都会覆盖先前列出的相同子对象的任何初始化器;151) 所有未明确初始化的子对象都将与具有静态存储期的对象隐式初始化相同。

另外,

如果具有静态或线程存储期的对象未被明确初始化,则:

  • 如果它具有指针类型,则初始化为 null 指针;

  • 如果它具有算术类型,则初始化为(正数或无符号)零;

  • 如果它是聚合体,则根据这些规则递归地初始化每个成员,并将任何填充初始化为零位;

  • 如果它是联合体,则根据这些规则递归地初始化第一个命名成员,并将任何填充初始化为零位;

现在,像下面这样的初始化语句

char str2[100] = {'a'};

根据上述规则,将会为str2[0]初始化为'a',并将str2[1]str2[99]设置为0。这个0值是字符串的终止符。

因此,任何存储在那里的值,都小于数组长度,最多只能到达length-1元素,都会自动以null结尾。

所以,您可以使用该数组作为字符串,并获得预期的字符串行为。


[1]: 聚合类型:

根据第6.2.5/P21章节

[...] 数组和结构体类型一起称为聚合类型。


你可能会想指出数组是“聚合”的,因为强调本身并没有意义。数组是一个聚合体,因此每个成员都按照上述规则进行初始化(递归)。如果元素是指针,则初始化为null,如果是算术类型,则初始化为零。 - Lundin
@Lundin 先生,好的,已添加备注。 - Sourav Ghosh
很抱歉,我只能标记一个答案为正确的 :( - Hemil

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