【问题标题】:ISO C95 array initialization guaranteeISO C95 数组初始化保证
【发布时间】:2020-06-17 12:01:24
【问题描述】:

我正在尝试查找确认或反驳以下声明的文档

char test[5]="";

导致缓冲区初始化为所有与 相同的空字符

memset(test,'\0',sizeof(test));

但无法找到(或理解/破译)任何东西。我专门在旧规范中寻找细节,C99 参考也可以。 谢谢

【问题讨论】:

  • 不知道C95是什么,但是是的,它是等价的。文档将遵循...
  • 你是说C99吗?
  • @Barmar 我确定它是重复的,正在寻找一个
  • 这能回答你的问题吗? C char array initialization
  • @M.M 我不知道 C95 标准修订版,所以假设它是一个错字/错误....实际上找到它,它是 C90 的 extension。我没有指向它的免费副本/草稿的链接。仍然不确定OP的意思。 FWIW C89 在port70.net/~nsz/c/c89/c89-draft.html#3.5.7中有一个非常相似的措辞

标签: c arrays language-lawyer string-literals aggregate-initialization


【解决方案1】:

这不是对您问题的完整答案,但有助于清除其他答案中的错误信息

在 ANSI C89 中,相关的标准文本是(第 3.5.7 节):

如果具有自动存储持续时间的对象未显式初始化,则其值是不确定的。

一个字符类型的数组可以由一个字符串字面量初始化,可选地用大括号括起来。字符串文字的连续字符(如果有空间或数组大小未知,则包括终止空字符)初始化数组的成员。

它只指定了字符串字面量对应的数组元素的初始化。所以尾随数组元素没有显式初始化,因此具有不确定的值。

还有一段:

如果列表中的初始化器数少于聚合的成员数,则聚合的其余部分应被隐式初始化,与具有静态存储持续时间的对象相同。

但这并不适用,因为我们不是从列表中初始化的(“列表”表示用大括号括起来的列表,而不是字符串文字)。


在 C90 中(我不确定是否可以合法链接),这些部分被重新编号,因此包含这些段落的部分变为 6.5.7。后一段的措辞也发生了变化:

如果大括号括起来列表中的初始化器少于聚合的成员,则聚合的其余部分应隐式初始化,与具有静态存储持续时间的对象相同。 p>


在 C90 TC1(HTMLPDF)中,上述内容保持不变。

然而,Defect Report 60 提出了一个关键问题:

当一个 char(或 wchar_t)数组用包含的字符数少于数组的字符串字面量进行初始化时,该数组的其余元素是否已初始化?

子条款 6.5.7 初始化,第 72 页,只说(强调我的):

如果大括号括起来的列表中的初始化程序少于聚合的成员,则聚合的其余部分应隐式初始化,与具有静态存储持续时间的对象相同。 p>

更正

在第 72 页的第 6.5.7 小节中,语义的倒数第二段(在示例之前),在逗号之后添加:

字符串字面量或宽字符串字面量中的字符或更少的字符,用于初始化已知大小的数组,以及字符或 wchar_t 类型的元素

看来,建议修复中的零初始化确实是标准编写者的意图,因为在C90 TC2 中,我们看到了同样的关键更改:

第 72 页

在第 72 页的第 6.5.7 小节中,语义的倒数第二段(在示例之前),在逗号之后添加:

字符串文字或宽字符串文字中用于初始化已知大小的数组以及字符或 wchar_t 类型的元素的字符或更少

给我们:

如果大括号括起来的列表中的初始化程序少于聚合的成员,或用于初始化已知大小数组的字符串文字或宽字符串文字中的字符更少,并且字符或元素wchar_t 类型 聚合的其余部分应隐式初始化,与具有静态存储持续时间的对象相同。

请注意,TC1 的日期是 1994 年,尽管它是在 1995 年出版的。TC2 的日期是 1996 年。令人困惑的是,DR60 的日期是 1993 年 7 月 16 日,因此早于 TC1。也许那时 TC1 的工作已经太先进了,无法处理新的缺陷报告并且积压了?在任何情况下,TC2 主要只是针对缺陷报告的一组更正,表明该更改首先出现在 C95 中,而不是在 C95 中,并且空终止符后字符的零初始化是 C89 标准编写者所拥有的有意的。


在 ISO C99(原始版本,没有技术勘误)中,该段落现在重新编号为 6.7.8/21 并再次更改。删除了“宽字符串文字”“字符或wchar_t类型的元素”的提及:

如果大括号括起来的列表中的初始化程序少于聚合的元素或成员,或用于初始化已知大小数组的字符串文字中的字符少于数组中的元素,聚合的其余部分应隐式初始化,与具有静态存储持续时间的对象相同。

意味着尾随数组元素被初始化为空字节。

(注意:原始 C99 可能是受版权保护的材料,所以我不能在上面发布指向它的链接。不过,这就是它所说的。Here 是最后一个免费提供的链接工作草案。之后还有两个草案,但 WG14 网站已将其删除。不过,该措辞在 N843 工作草案中,并且仍然存在于后来的包含 TC3 的 C99 中。


我找不到任何 C95 (ISO/IEC 9899:1990/AMD1:1995) 的免费副本。因此,我无法准确回答在 C89 和 C99 之间的哪个点进行了“宽字符串文字”和“wchar_t”更改。此外,C99 基本原理文档中也没有提及该主题。

当然,C99 的行为可能是 C89 作者的意图,而缺失的文本是疏忽,但在没有任何此类文档的情况下,我们无法得出任何结论,并且可能是从那时起不初始化尾随元素的编译器。

希望拥有这些文件(或倾向于从 ISO 商店购买它们!)的其他人可以提供准确的答案。

【讨论】:

  • 我稍后会检查我的副本。 C90 标准的作者可能隐含地假设该数组将被零填充,而后来的版本只是明确了该假设。
  • @KeithThompson 好的,那太好了。当然,除非他们记录下来,否则我们当然无法知道他们的意图,因此我们必须将文本的字母作为指定的行为(更重要的是,编译器供应商可能会这样做)。我确实记得在 c.l.c 时代之前讨论过这个问题,甚至可能在 C99 发布之前,可能是由于人们观察到编译器没有初始化尾随元素。这些档案中可能有一些相关的东西
  • @KeithThompson 我做了答案社区维基,所以一旦您可以访问您的文档,请随时编辑
  • 谢谢。发布我自己的答案似乎更容易。
  • 关于 C95 - 我在网上找不到完整版本,但 lysator.liu.se/c/na1.html 有一个看起来像是官方摘要的帖子,它没有提到使用字符串字面量进行数组初始化。跨度>
【解决方案2】:

快速总结

C99 及更高版本保证剩余的字符初始化为零。 C89/C90/C95 不做此保证,也不指定剩余字符的值。这可能是一个无意的疏忽,我推测大多数或所有 C99 之前的编译器无论如何都会对剩余的字符进行零初始化。如果您使用的是符合 C99 或更高版本的编译器,则保证零初始化。

血淋淋的细节

char test[5]="";

由于 C89/C90 标准中的缺陷,这只能保证将 test[0] 初始化为 '\0'test 的其他元素未指定。

C95 修正案没有解决这个问题。

C99 标准纠正了这一缺陷,要求将test 初始化为全零。

另一个例子:

char foo[5] = "foo";

在 C89/C90、C95 中,语言保证 foo[0]=='f', foo[1]=='o', foo[2]=='o', foo[3]=='\0',但对 foo[4] 的值只字未提。在 C99 及更高版本中,它保证被初始化,就像你写的一样:

char foo[5] = { 'f', 'o', 'o', '\0' };

在所有版本的 C 标准中,保证 foo[4]=='\0'

引文

1989 ANSI C 标准和 1990 ISO C 标准是等效的,不同之处仅在于非规范的介绍性材料和部分的重新编号。 1995 年的修正案更新了标准,但不影响数组初始化。

1990 ISO C 标准第 6.5.7 节说:

一个字符类型的数组可以由一个字符串字面量初始化,可选地用大括号括起来。字符串文字的连续字符(如果有空间或数组大小未知,则包括终止空字符)初始化数组的元素。

以及稍后在同一部分中:

如果大括号括起来的列表中的初始化程序比聚合的成员少,则聚合的其余部分应隐式初始化,与具有静态存储持续时间的对象相同。

它指定对于大括号括起来的列表将尾随成员初始化为零,但对于字符串文字初始值设定项不做相同的声明。 (我推测这是一个无意的疏忽,并且大多数编译器无论如何都会将剩余的元素填充为零,因为在某些情况下他们已经不得不这样做了。)

每个版本的 C 标准都有一组与之相关的缺陷报告:

C90 Defect Report #060,由 P.J. Plauger 和/或 Larry Jones 于 1993 年提交,提出了这个问题:

char(或 wchar_t)数组被初始化为 包含的字符少于数组的字符串文字是 数组的剩余元素是否已初始化?
子条款 6.5.7 初始化,第 72 页,只说(强调我的):

如果大括号括起来列表中的初始化器少于聚合的成员,则聚合的其余部分应隐式初始化,与具有静态存储持续时间的对象相同。 p>

对该缺陷报告的响应导致 C99 标准第 6.7.8 节第 21 节(强调添加)中的措辞修改:

如果大括号括起来的列表中的初始化程序少于聚合的元素或成员,或用于初始化已知大小数组的字符串文字中的字符少于数组中的元素,聚合的其余部分应隐式初始化,与具有静态存储持续时间的对象相同。

【讨论】:

    【解决方案3】:

    来自 C 标准(6.7.9 初始化)

    10 如果具有自动存储时长的对象未初始化 明确地说,它的值是不确定的。如果一个物体有静电 或者线程存储时长没有显式初始化,则:

    ——如果是指针类型,则初始化为空指针;

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

    ...

    21 如果大括号括起来的列表中的初始值设定项比那里少 是聚合的元素或成员,或 用于初始化已知大小的数组的字符串文字 是数组中的元素,聚合的其余部分应为 隐式初始化与具有静态存储的对象相同 持续时间。

    这意味着在这个声明中

    char test[5] = "";
    

    数组的所有五个元素都初始化为零。第一个元素由字符串文字的终止零显式初始化,所有其他元素都以与具有静态存储持续时间的对象相同的方式隐式初始化。

    至少从 C99 标准开始有效。

    下面有一个演示程序,显示了用零初始化字符数组的不同方法。

    #include <stdio.h>
    
    int main(void) 
    {
        enum { N = 5 };
    
        char s1[N] = "";
        char s2[N] = { "" };
        char s3[N] = { 0 };
        char s4[N] = { [0] = 0 };
        char s5[N] = { [N-1] = 0 };
    
        char * s[] = { s1, s2, s3, s4, s5 };
    
        for ( size_t i = 0; i < sizeof( s ) / sizeof( *s ); i++ )
        {
            for ( size_t j = 0; j < N; j++ )
            {
                printf( "%d", s[i][j] );
            }
            putchar( '\n' );
        }
        return 0;
    }
    

    程序输出是

    00000
    00000
    00000
    00000
    00000
    

    【讨论】:

    • 你引用哪个版本的 C 标准?
    • @M.M 我引用了 C11 标准。但我确信这同样适用于 C89 标准。
    • C89 标准不包含“或用于初始化已知大小数组的字符串文字中的字符数少于数组中的元素数”的规定
    • @M.M 这不只是澄清而不是实际添加吗?
    • @EugeneSh。不明白你的意思;这是我们正在谈论的规范性文本
    【解决方案4】:

    从代码清晰的角度来看,如果数组的目的是保存字符串,那么

    char test[5] = "";
    

    用长度为零的字符串初始化数组,其余字节无关紧要。如果它们确实重要,那么数组并不是真正的字符串,您应该使用

    char test[5] = {0};
    

    澄清一下。

    【讨论】:

    • 这两个完全一样。
    • @EugeneSh。它们在 C11 中相同,但在 C89 中不同
    • 这就是我一直在寻找的东西,试图准确地确定规范的不同之处,但还没有找到文档。谢谢
    • @EugeneSh.,它们与编译器完全相同。但代码不仅仅与编译器通信——它也与人类通信。
    猜你喜欢
    • 1970-01-01
    • 2020-10-24
    • 2011-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多