说我有这段代码:
void foo() {
char s[10];
char v1 = s[0]; // UB
char v2 = s[10]; // also UB
}
void bar() {
char s[10];
strcpy(s, "foo");
char v3 = s[3]; // v3 is zero
char v4 = s[0]; // v4 is 'f'
char v5 = s[4]; // What?
}
由于在strcpy中访问s[0]
到s[3]
的地址,并且s [0]到s [9]在连续内存中,我想整个数组应该包含一些值(包括不确定的)。
关于v5
的操作是否明确定义?或者v5
只是一个不确定的值(没有绊倒任何UB)?
如果数组是int类型并且仍然部分分配了怎么办?
答案 0 :(得分:3)
它不能是未定义的,因为那里的char可能有一个陷阱表示,因为6.2.6.1p5表示访问具有字符类型的任何东西都是明确定义的。
,可能未定义指定自动存储持续时间的对象的左值 可能已经声明了寄存器存储类用于a 上下文需要指定对象的值,但是 对象未初始化。
所以问题是,数组是否已使用寄存器存储类声明?
答案是不,它不可能,因为你正在索引它。索引是根据6.5.2.1p2
定义的(
后缀表达式后跟方括号[]中的表达式 是数组对象元素的下标。该 下标运算符[]的定义是E1 [E2]与...相同 (*((E1)+(E2)))。由于适用于的转换规则 binary +运算符,如果E1是数组对象(等效于指针 到数组对象的初始元素),E2是一个整数, E1 [E2]表示E1的第E2个元素(从零开始计数)。 )
就数组转换为第一个元素的地址而言,但是对于寄存器分类数组,这种转换根据项目点未定义:
附录J.2 Undefined behavior中的具有数组类型的左值被转换为指向初始值的指针 数组的元素,数组对象具有寄存器存储类 (6.3.2.1)。
,表示无法声明数组register
。
Storage class specifiers进一步阐述了这一点:
使用storage-class声明的对象的任何部分的地址 无法明确地计算说明符寄存器(通过使用 一元&操作员,如6.5.3.2)中所述或隐含地(由 将数组名称转换为指针,如6.3.2.1中所述。从而, 唯一可以应用于声明的数组的运算符 存储类说明符寄存器是sizeof和_Alignof
(换句话说,虽然语言允许寄存器阵列,但它们基本上无法使用)。
因此,代码如下:
char unspecified(void){ char s[1]; return s[0]; }
将返回未指定的值,但不会使程序的行为未定义。
答案 1 :(得分:0)
该标准的作者并不认为有必要明确描述迄今为止每个编译器始终以相同方式处理的极端情况,并且他们认为如果其设计者不是任何实现可能会有不同的行为。故意是钝的。涉及部分书面聚合的情景属于这一类。
数组下标的行为定义为获取数组的地址,对结果指针执行算术运算,然后访问结果地址。我个人认为它应该被定义为一种单独的操作,具有略微不同的极端情况,从显式获取数组的地址,执行指针算术和转换结果,但标准根据这些步骤定义操作。因此,不是故意钝的编译器应该将使用下标运算符访问的数组视为其地址被占用的对象,并且因此可以访问该数组是否已被写入。但是,这仍然会对此类代码的行为提出疑问。
假设“unsigned char”为8位且“unsigned”为24或更多,则返回以下值:
unsigned test1(unsigned char *p)
{
unsigned x=p[0];
unsigned y=p[0];
unsigned z=y;
return x | (y << 8) | (z << 16);
}
unsigned test(void)
{
unsigned char foo[1];
return test1(foo); // Note that this takes the address of 'foo'.
}
就个人而言,我怀疑要求为test1
生成的代码必须表现得好像x
,y
和z
都具有相同的价值才会有任何不利之处在0..255范围内,或 - 在绝对最小值 - 表现为y
和z
保持相同的值。我不认为标准的作者会认为任何非钝的实现都不会那样,但标准实际上并不需要它,有些人似乎认为要求这样的行为会不适当地限制优化
答案 2 :(得分:-3)
是的,这是未定义的行为。
部分分配的数组是一个包含初始化和未初始化内存区域的数组。读取未初始化的内存区域是未定义的行为,就像读取任何其他未初始化的内存区域一样。