26

以下程序是 C 语言中严格符合的程序吗?我对 c90 和 c99 感兴趣,但 c11 的答案也可以接受。

#include <stdio.h>
#include <string.h>

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使其与a. 但是,C99 似乎没有提供这样的保证。从第 6.5.16.1 p2 节:

简单赋值( =) 中,右操作数的值被转换为赋值表达式的类型,并替换存储在左操作数指定的对象中的值。

在复合类型的上下文中,“转换”和“替换”是什么意思?

最后,考虑同一个程序,除了 和 的定义ab全局的。程序会是一个严格遵守的程序吗?

编辑:只是想在这里总结一些讨论材料,而不是添加我自己的答案,因为我真的没有自己的创作。

  • 该程序不严格符合。由于赋值是按值而不是按表示,因此b.array可能包含也可能不包含与a.array.
  • a不需要转换,因为它与 的类型相同b,但替换是按值,并且逐个成员完成。
  • 即使a和中的定义b是全局的,后赋值b.array也可能包含也可能不包含与 不同的位设置a.array。(关于填充字节的讨论很少b,但发布的问题不是关于结构比较。c99 没有提到如何在静态存储中初始化填充,但 c11 明确声明它是零初始化的。)
  • 附带说明一下,如果使用from初始化,memcmp则已明确定义。bmemcpya

我感谢所有参与讨论的人。

4

2 回答 2

4

在 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但不影响值的位。这与其他答案的结论相同,但我认为标准中的这些引用将是很好的附加背景。


如果您执行 amemcpy那么 thememcmp将始终返回 0 并且程序将严格符合。memcpy复制ainto的对象表示b

于 2012-08-16T21:13:21.480 回答
0

我的观点是它是严格符合的。根据 Eric Postpischil 提到的 4.5:

严格遵守的程序应仅使用本国际标准中指定的语言和库的那些特性。它不应产生依赖于任何未指定、未定义或实现定义的行为的输出,并且不应超过任何最小实现限制。

有问题的行为是 的行为memcmp,这是定义明确的,没有任何未指定、未定义或实现定义的方面。它适用于表示的原始位,而不知道有关值、填充位或陷阱表示的任何信息。因此,在这种特定情况下的结果(但不是功能memcmp取决于存储在这些字节中的值的实现。

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自身的行为已明确定义。在输出发生之前,该子句没有说明功能的深度。

所以它不是严格符合的。

编辑2:

我不太确定它在memcpy用于复制结构时会变得严格一致。根据附件 J,未指定的行为发生在a初始化时:

struct S a = { { 1, 2 } };

即使我们假设填充位不会改变并且memcpy总是返回 0,它仍然使用填充位来获取结果。它依赖于它们不会改变的假设,但标准中对此没有任何保证。

我们应该区分结构中用于对齐的填充字节和特定本机类型(如int. 虽然我们可以安全地假设填充字节不会改变,但这只是因为没有真正的原因,同样的情况不适用于填充位。该标准提到奇偶校验标志作为填充位的示例。这可能是实现的软件功能,但也可能是硬件功能。因此,可能存在用于填充位的其他硬件标志,包括因任何原因在读取访问时发生变化的硬件标志。

我们很难找到这样一个奇特的机器和实现,但我没有看到任何禁止这样做的东西。如我错了请纠正我。

于 2012-08-16T21:23:06.567 回答