在int数组上使用memcmp严格符合吗?
以下程序是否是C中严格符合的程序? 我对c90和c99感兴趣,但c11的答案也是可以接受的。
#include #include struct S { int array[2]; }; int main () { struct S a = { { 1, 2 } }; struct S b; b = a; if (memcmp(b.array, a.array, sizeof(b.array)) == 0) { puts("ok"); } return 0; }
在对不同问题的答案的评论中 ,Eric Postpischil坚持认为程序输出将根据平台而改变,主要是由于未初始化填充位的可能性。 我认为结构赋值会覆盖b
所有位与b
中的相同。 但是,C99似乎并没有提供这样的保证。 从第6.5.16.1节第2节:
在简单赋值 (
=
)中,右操作数的值将转换为赋值表达式的类型,并替换存储在左操作数指定的对象中的值。
在复合类型的背景下,“转换”和“替换”是什么意思?
最后,考虑相同的程序,除了a
和b
的定义是全局的。 该计划是否是严格遵守的计划?
编辑:只是想在这里总结一些讨论材料,而不是添加我自己的答案,因为我没有真正拥有自己的创作。
- 该计划并非严格遵守。 由于赋值是按值而不是表示,因此
b.array
可能包含也可能不包含与a.array
不同的位。 -
a
不需要转换,因为它与b
类型相同,但是替换是按值,并且由成员完成。 - 即使
a
和b
中的定义是全局的,post assignment,b.array
可能包含也可能不包含与a.array
不同的位。 (关于b
的填充字节几乎没有讨论,但是发布的问题不是关于结构比较.c99没有提到如何在静态存储中初始化填充,但c11明确声明它是零初始化。) - 另一方面,如果
b
用来自a
memcpy
初始化,则会同意memcmp
定义良好。
我要感谢所有参与讨论的人。
在C99§6.2.6中
§6.2.6.1总则
1除本条款规定外,所有类型的陈述均未指明。
[…]
4 [..]具有相同对象表示的两个值(除NaN之外)比较相等,但比较相等的值可能具有不同的对象表示。
6当值存储在结构或联合类型的对象中(包括在成员对象中)时,对应于任何填充字节的对象表示的字节将采用未指定的值。 42)
42)因此,例如,结构分配不需要复制任何填充比特。
43)具有相同有效类型T的对象x和y在作为类型T的对象访问时具有相同的值,但在其他上下文中具有不同的值。 特别是,如果为类型T定义了==,那么x == y并不意味着memcmp(&x,&y,sizeof(T))== 0.此外,x == y并不一定意味着x和y具有相同的价值; 对类型T的值的其他操作可以区分它们。
§6.2.6.2整数类型
[…]
2对于有符号整数类型,对象表示的位应分为三组:值位,填充位和符号位。 不需要任何填充位; […]
[…]
5未指定任何填充位的值。[…]
在J.1未指定的行为
- 在结构或联合中存储值时填充字节的值(6.2.6.1)。
[…]
- 整数表示中任何填充位的值(6.2.6.2)。
因此, a
和b
的表示中可能存在不同而不影响该值的位。 这与其他答案的结论相同,但我认为标准中的这些引用将是良好的附加背景。
如果你做一个memcpy
那么memcmp
将始终返回0并且程序将严格符合。 memcpy
将a的对象表示复制到b
。
我的意见是它严格遵守。 根据4.5,Eric Postpischil提到:
严格符合的程序应仅使用本国际标准中规定的语言和库的特征。 它不应产生依赖于任何未指定,未定义或实现定义的行为的输出,并且不得超过任何最小实现限制。
有问题的行为是memcmp
的行为,这是明确定义的,没有任何未指定,未定义或实现定义的方面。 它适用于表示的原始位,而不知道有关值,填充位或陷阱表示的任何信息。 因此,在这种特定情况下, memcmp
的结果 (但不是function )取决于存储在这些字节中的值的实现。
6.2.6.2中的脚注43):
具有相同有效类型T的对象x和y在作为类型T的对象访问时具有相同的值,但在其他上下文中具有不同的值。 特别是,如果为类型T定义了==,那么x == y并不意味着memcmp(&x,&y,sizeof(T))== 0.此外,x == y并不一定意味着x和y具有相同的价值; 对类型T的值的其他操作可以区分它们。
编辑:
进一步思考,我不再那么严格遵守,因为这样:
它不应产生依赖于任何未指明的[…]的输出。
很明显, memcmp
的结果取决于表示的未指定行为,从而实现了该子句,即使memcmp
本身的行为已被很好地定义。 在输出发生之前,该子句没有说明function的深度。
所以它并不严格符合。
编辑2:
当使用memcpy
复制结构时,我不太确定它是否会严格符合要求。 根据附件J,初始化a
时会发生未指定的行为:
struct S a = { { 1, 2 } };
即使我们假设填充位不会改变并且memcpy
总是返回0,它仍然使用填充位来获得其结果。 它依赖于它们不会改变的假设,但标准中并没有保证这一点。
我们应该区分结构中的填充字节,用于对齐,以及特定本机类型(如int
填充位。 虽然我们可以安全地假设填充字节不会改变,但仅仅因为它没有真正的原因,同样不适用于填充位。 标准提到奇偶校验标志作为填充位的示例。 这可以是实现的软件function,但它也可以是硬件function。 因此,可能存在用于填充比特的其他硬件标志,包括出于任何原因在读取访问上改变的标志。
我们将很难找到这样一个奇特的机器和实现,但我没有看到任何禁止这一点。 如我错了请纠正我。