【问题标题】:Can a type in C have more than one object representation?C 中的一个类型可以有多个对象表示吗?
【发布时间】:2019-12-02 07:10:51
【问题描述】:

C99 标准第 6.2.6.1 8 节规定:

当运算符应用于具有多个对象的值时 表示,不影响使用哪个对象表示 结果的值 (43)。值存储在对象中的位置 该值具有多个对象表示的类型,它 未指定使用哪种表示,但是一个陷阱表示 不会生成。

我理解对象是指内存中的一个位置(字节),值是基于用于访问它的类型对这些字节的解释。如果是,那么:

  1. 一个值如何有多个对象表示?
  2. 一个类型如何可以有多个对象表示一个值?

标准在脚注中添加了以下内容:

不过,我还不清楚。有人可以帮我简化一下并举例说明吗?

【问题讨论】:

  • 例如,如果使用符号/大小对数字进行编码,则将有两个值表示0:正零和负零。
  • 有一篇关于浮点数及其编码(即对象表示)here的相关文章。例如,值1.5 可以有多个n 位固定或浮点类型的表示。关于您的附录 43)我认为这与解释位有关,例如作为有符号/无符号整数,或作为浮点数等。
  • @Blaze:是的,使用符号和大小,是表示数学零的两种方式,但它们在 C 标准的含义上不是同一个值。请注意,我们可以区分它们:如果x 为正零,y 为负零,并且它们是 32 位,则 x|1 为 +1,y|1 为 -1。正如我们从脚注的最后一句中看到的,xy 上的“其他操作”区分了它们,表明它们没有与 C 定义的术语相同的“值”,即使 x == y 评估为真。 C 将“值”定义为“精确含义”(3.19),而不是数学值。
  • @EricPostpischil 啊,所以即使按位运算,它们也需要具有相同的值。感谢您的详细解释。

标签: c


【解决方案1】:

对象是一个存储区域(内存),可以包含某种类型的值 [C18 3.15]。

对象表示是构成对象内容的字节 [C18 6.2.6.1]。

并非对象表示中的每个可能的字节组合也必须对应于类型的值(不对应的对象表示称为陷阱表示 [C18 3.19.4]) .

并不是对象表示中的所有位都必须参与表示一个值。考虑以下类型:

struct A
{
    char c;
    int n;
};

编译器被允许(并且通常会)在该结构的成员cn 之间插入填充字节,以确保n 的正确对齐。这些填充字节是struct A 类型对象的一部分。因此,它们是对象表示的一部分。但是这些填充字节的值对对象中存储的A类型的逻辑值没有任何影响。

假设我们在一个目标平台上,其中字节由 8 位组成,int 由 4 个字节(以小端序排列)组成,cn 之间有 3 个填充字节以确保 @ 987654330@ 从偏移量开始,该偏移量是 4 的倍数。值 (struct A){42, 1} 可以存储在对象中

2A 00 00 00 01 00 00 00

但它也可以存储在一个对象中

2A FF FF FF 01 00 00 00

或其他任何可能的填充字节。这些字节序列中的每一个都是相同类型struct A的逻辑值的有效对象表示。

这也是脚注的内容。如果您有两个对象xy,每个对象都包含struct A 类型的相同值的不同对象表示,那么x == y 将评估为真,而简单地执行memcmp() 将不会因为memcmp()只是比较对象表示的字节,而不考虑这些对象中存储的逻辑值实际上是什么......

【讨论】:

  • C99 的基本原理提到委员会被告知有一台机器使用 2 个 16 位 short 来组成一个 32 位 int,忽略较低 short 的符号位,并且这是向标准添加填充字节的原因之一。
  • @ninjalj:这还不清楚。首先,2个16位的short组成一个32位的int,没有多余的字节可以作为填充字节,那么你指的是什么填充字节?其次,如果忽略其中一个符号位,则int 只有 31 个位,而不是 32 个。您的意思是一个符号位被视为值位,而不是被忽略?
  • 填充不必以字节为单位。 C标准特别有一个填充位的概念,它是整数类型的对象表示的所有位,不参与该整数类型的值表示......我猜这就是ninjalj所指的......
  • 还要考虑一个包含位域的结构。除非所有位域成员都完全适合编译器为它们分配的任何存储单元,否则将会留下单独的位,它们是对象表示的一部分,但与表示结构类型的值无关……
  • @ninjalj 我在哪里可以找到您提到的 C99 基本原理?
【解决方案2】:

一个值如何有多个对象表示?
一个类型怎么能有多个对象表示一个值?

是的,不让每个位模式对应不同的值。

通常首选 1 位模式,canonical form,以及其他很少通过正常方式生成的模式。

  1. x86 extended precision format 包含的位模式与其他位模式的值相同 - 即使符号相同。研究“伪非正规”和“非正规”位模式。

副作用是,由于这种冗余,这种 80 位编码无法实现 280 个不同的值。 (即使考虑了非数字)

  1. 使用2 doublelong double 进行编码具有类似的影响。

2 double 表示 long double 值的过度简化示例:

1000001.0 + 0.0(规范形式)与1000000.0 + 1.0 相同的

  1. Decimal floating point 也有这个问题。

由于有效数字没有被规范化,大多数少于 16 位有效数字的值都有多种可能的表示形式; 1×102=0.1×103=0.01×104等


由于相同值的多个位模式会减少可能数字的范围,因此与非冗余编码相比,此类编码往往会失宠。一个影响是,我们现在看起来不像他们了。

它们存在的原因首先是为了便于硬件实现或简单易于定义(让我们为我们的新 FP 格式显式编码最高有效数字 - 使用隐含的数字是如此令人困惑)。


@Eric 提出了一个关于 valueoperator 的有趣评论,这取决于:

如果将运算符应用于具有多个对象表示的值,则使用哪个对象表示不应影响结果的值。 C17 § 6.2.6.2 8

鉴于x = +0.0y = -0.0 具有相同的数值 0,仍然可以作为具有不同值的运算符 / 区分它们如1.0/x != 1.0/y

上述各种 FP 示例仍有许多其他情况,其中 x,y 具有不同的位模式但具有相同的

【讨论】:

  • C 标准中“值”的含义不是数字或数学值。具有不同位的两个浮点对象表示相同的数学值这一事实并不意味着它们表示相同的 C 值。如果您可以使用对 T 类型的值的操作来区分两个 T 类型的对象,那么这些对象不代表相同的值。例如,如果 signbit(x) != signbit(y)xy 具有不同的 C 值,即使它们是 +0 和 -0。 C 标准允许一个值的多种表示形式是为了使非值位(例如填充位)发生变化。
  • @EricPostpischil value 在 6.2.6.1 8 中的使用包括 a == b 但位模式不同的情况。那是OP关于为什么和这个答案的例子的问题。此答案未建议 +0, -0 问题,因此不能作为反例。 “一个值的多个表示是非值位,例如填充位,变化”是正确的,是另一个例子,但不否定各种 FP。
  • 表示相同值的多个值位模式可能属于 C 标准对值的含义,但前提是根本无法使用对适用类型值的操作来区分它们。这意味着没有函数返回符号位,没有函数返回实际(相对于标准化)指数或有效数,等等。这就是你提议的吗?如果是这样,我建议在答案中澄清这一点。
  • @EricPostpischil 同意“无法使用 运算符 区分它们。我找不到将这种限制扩展到函数的 C 支持。也许你知道一个?答案已修改。
  • 脚注说的是“操作”,而不是“操作员”。但是,这并不重要:如果您可以将xy 传递给区分它们的函数,那么xy 的值就会有所不同,因为它们是按值传递的。如果 30e0 和 3e1 与 C 标准所指的值相同,并且您将编码 30e0 的位传递给函数,则 C 实现将有权传递 3e1,因为它们是相同的值。因此,能够通过将 xy 按值传递给函数来提取指数,这表明它们的值是不同的。
【解决方案3】:

举例说明?

例如,在表示 float 类型的编译器上,根据 IEEE 754-2008 标准使用 decimal floating point,假设星号正确对齐 - CHAR_BIT=8sizeof(int)==4floats 的宽度为 32 -bits 没有填充位并且编译器使用 little endian,以下代码(使用 gcc9.2 和 -Dfloat=typeof(1.0df) 测试):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
        float a, b;
        // simulate `a = 314` and `b = 314` with a compiler
        // that chose to use different object representation for the same value
        memcpy(&a, (int[1]){0x3280013a}, 4);
        memcpy(&b, (int[1]){0x32000c44}, 4);
        printf("a = %d, b = %d\n", (int)a, (int)b);
        printf("a %s b and memcpy(&a, &b) %s 0\n",
               a == b ? "==" : "!=",
               memcmp(&a, &b, sizeof(a)) == 0? "==" : "!=");
}

应该(可能)输出:

a = 314, b = 314
a == b and memcpy(&a, &b) != 0

【讨论】:

  • 我开始按照这些思路写一个答案,但我认为它不正确。 C 中的“值”不是数值。 3.19 将“值”定义为“对象内容在解释为具有特定类型时的精确含义”。如果那个“精确含义”仅仅是数值,那么脚注中的最后一句,“此外,x == y 并不意味着xy 具有相同的值;对T 类型值的其他操作可能会区分它们。”不可能是真的。为了使 +0 和 -0 具有不同的值,它们的值必须大于数字 0。
  • 例如,signbit(+0.)signbit(-0.) 返回不同的东西。所以+0.-0. 的“精确含义”不仅仅包括数学值0。还要注意== 的规范并没有说如果两个对象具有相同的值它会产生真值。它说如果两个对象相等,它会产生 true 。因此,它在与价值不同的意义层面上运作。我认为这为我们留下了 Michael Kenzel 答案中的含义——当对象包含的位多于用于值本身的位时,一个值可以具有多个对象表示。
  • 这包括 10 字节浮点数,使用 16 字节进行对齐优势,当前成员不填充字节的联合,以及带有填充的结构。
  • 噢,我明白了。我只想回答示例部分,留下更难的“究竟是什么值”问题,但示例也是错误的——它们没有显示它,它们只是比较比较相等的变量的不同值。我正在尝试使用decimal32_t 准备一个示例,我认为它可能会更好。
【解决方案4】:

具有多个表示的值的一个简单示例是 IEEE 浮点零。它具有“正零”和“负零”表示。

注意 符合 IEC 60559 的实现必须区分正零和负零,因此在这样的实现中,它们是不同的值,而不是相同值的不同表示。但是,实现不需要符合 IEC 60559。即使底层硬件区分 +0 和 -0,也总是为零符号位重新运行相同的值。

在符号和大小机器上,整数零也有不止一种表示形式。

在像 16 位 8086 这样的分段架构上,“长”指针有不止一种表示,例如 0x0000:0x0010 和 0x0001:0x0000 是相同指针值的两种表示。

最后,在任何带有填充的数据类型中,填充位都不会影响值。示例包括带有填充孔的结构。

【讨论】:

  • this answer 上查看我的注释——我认为这不正确。 +0 和 -0 是 C 定义的不同值,即使它们比较相等。 C 标准定义的“值”是对象的“精确含义”,包括它与signbit 函数的行为,而不仅仅是它的数学值。
  • 据我了解 C 标准中“值”的含义,符号和大小格式的零并不是具有多个表示形式的值的示例。从脚注最后一句可以看出,如果对T类型的操作可以区分T类型的两个对象,那么它们就有不同的值。如果x 为正零且y 为负零,则x|1 为+1 但y|1 为-1。因此,xy 在 C 标准的含义中没有相同的值,即使 x == y 的计算结果为 true。
猜你喜欢
  • 1970-01-01
  • 2017-06-17
  • 2021-10-04
  • 2011-02-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多