访问部分初始化的数组是否是未定义行为?

4
有关访问未初始化变量是否产生未定义行为的讨论已经进行了几次(例如在这个SO答案中),我也查看了关于未确定值和未定义行为的这份在线C11草案标准

从SO和标准中发现(也许我有所疏漏),当访问未初始化变量时,未定义行为与陷阱表示或具有(隐式)寄存器存储类别的可能性有关。

但是,如果相应的变量是一个数组(无法获得寄存器存储类别),而且数据类型不能有陷阱表示(例如根据6.2.6.1p5对字符类型的定义)呢?

那么,访问这样的值仍然会产生未定义行为吗?

int main () {
    char output[10];
    for (int i=0; i<10; i+= 2) {  // initializing every 2nd element only
        output[i] = '0' + i;
    }
    char c = output[1]; // accesses something "uninitialized"; But is it UB?
    printf("%c\n", c);  // prints probably garbage; But what if I don't care?
    return 0;
}

5
@PeterJ 好的,那请将其作为答案,并解释为什么,并且用标准中的引用来支持您的观点。我会很乐意给您点赞。 - Petr Skocik
1
@PeterJ那为什么不链接其中一个已经回答过的10000000000次呢? - interjay
1
@PeterJ 未初始化的自动指针通常可以声明为“register”。但是在这段代码中,数组不能这样做,这就是我理解的区别。 - Petr Skocik
3
@PeterJ: 这个问题并没有被多次回答过。实际上,即使使用标量而不是数组的更简单版本的这个问题已经被回答了,但是答案是矛盾的(有些人说是未定义行为,有些人则说不是未定义行为,两种观点对于普通人来说都似乎是正确的)。 - John Zwinck
1
另一个有意义的评论是关于使用register存储类说明符声明数组的C11标准(草案n1570)§6.7.1(6和注释121)。虽然实现是定义的,但没有什么阻止使用register存储类说明符声明数组,使所有具有自动存储的数组都属于“可能已用寄存器存储类声明”的范畴,进一步表明访问任何未初始化的值都是未定义行为。这仍然不太清楚... - David C. Rankin
显示剩余16条评论
2个回答

3
这种类型的问题和讨论总是具有挑战性,因为它需要解释C标准,而在许多方面,C标准并不是为了清晰而编写的,而更多地是各个派系之间达成妥协和共识所产生的结果。经过多次阅读后,很明显花费更多的时间来讨论要包含什么或不包含什么,而不是如何使其更易于阅读和理解。
继续评论中的内容,我认为我们都可以基于评论和答案中引用的次数达成一致,即C11标准(草案n1570)§6.3.2.1左值、数组和函数指针(¶2)适用。
如果lvalue指定了具有自动存储期的对象,并且该对象可以使用寄存器存储类声明(从未取其地址),并且该对象未初始化(未使用初始化程序声明且在使用之前未执行任何分配给它的操作),则行为未定义。(强调我的)
问题是,“自动存储的数组是否可以使用寄存器存储类声明?” 乍一看,显而易见的想法是,“带有寄存器存储类说明符的数组?那太愚蠢了,你不能获取地址,你怎么能访问值?” 鉴于§6.2.5类型(注释36)) “当访问数组成员时,此类对象的地址会隐式地被取出。”
首先的想法通常是错误的,因为具有自动存储的数组允许使用寄存器存储类。§ 6.7.1 (6 & comment 121) 以下代码是完全合法的,虽然可能不是很有用。
#include <stdio.h>

int main (void) {

    register int a[] = { 1, 2, 3, 4 };
    register size_t n = sizeof a / sizeof (int);

    printf ("n : %zu\n", n);

    return 0;
}

只有 sizeof_Alignof 这两个运算符可以应用于使用存储类说明符 register 声明的数组。(详见 § 6.7.1(注释121)

根据上述内容,并且给定数组中任何未初始化的元素 "未初始化(未声明初始化程序并且在使用之前没有执行分配),行为是未定义的。"

针对您的特定情况:

    char c = output[1]; // accesses something "uninitialized"; But is it UB?

output[1] 表示一个自动存储期对象,它可以被声明为寄存器存储类(从未取地址),并且该对象未初始化(未使用初始值进行声明并且在使用前未对其进行赋值),这种情况的行为是未定义的。


是的,数字打错了。正在修正。无论你把数组的元素称为“element”还是“member”,这在很大程度上取决于个人口味。两者都可以明确地标识出组成数组的“事物”之一。 - David C. Rankin
我并不是想暗示你不能把数组元素称为数组成员。我想说的是标准并没有这样使用这个术语。可以看到每一次它使用的词都是“成员”和“元素”。(此外,上下文表明,“成员”在这里意味着结构体或联合体的成员,而与其他任何事情无关。) - Jeroen Mostert
1
我无法找到几个月前在另一个版本的这个问题中进行的长时间讨论 - 它可能已经被我的明确反对而“移至聊天”,使其无法搜索 - 但要点是,C编译器实际上处理J.2中的裸语句:“如果[...]在使用时值是不确定的,则行为未定义”,即使Annex J不是规范性的。这适用于所有值,无论如何声明或分配以及它们是否具有trap reps,甚至适用于“unsigned char”。 - zwol
1
我敢打赌,编译器供应商会声称这个简洁的陈述表达了委员会的真实意图,并且在规范文本与之不符的情况下,标准是有缺陷的。 - zwol
1
你应该买半打咖啡来搭配甜甜圈,因为我认为你的赌注是安全的。归根结底,尝试访问未初始化的值是未定义行为的简单规则--无论您必须经过多少标准上的花式引用才能针对数组元素建立简明的引证。 - David C. Rankin
显示剩余4条评论

2

根据C标准(6.2.6 类型的表示,6.2.6.1 通用):

5 某些对象表示不需要表示对象类型的值。如果对象的存储值具有这种表示,并且被lvalue表达式读取没有字符类型,则行为是未定义的....

因此,对于字符数组,没有未定义的行为。

字符类型的对象没有陷阱表示。


2
这只是其中的一部分。还有关于未初始化值的6.3.2.1/2。 - interjay

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