陷阱表示法

97
  1. 什么是C语言中的“trap representation”(可能需要一些示例)?这是否适用于C ++?

  2. 给定以下代码...

  3. float f=3.5;
    int *pi = (int*)&f;
    

    假设 sizeof(int) == sizeof(float),那么 f*pi 是否具有相同的二进制表示/模式?

3个回答

132
  1. 陷阱表示法是C99中(我记得C89不使用这个术语)用于描述位模式的一个总称,这些位模式适合于类型所占用的空间,但如果用作该类型的值,则会触发未定义行为。该定义在第6.2.6.1p5节中(涉及到6.2.6的所有内容),我不会在此引用它,因为它很长而且令人困惑。存在这种位模式的类型被认为“具有”陷阱表示法。没有任何类型需要具有陷阱表示法,但标准保证将unsigned char排除在具有陷阱表示法之外(6.2.6.1p5,6.2.6.2p1)。

    标准给出了两个假设的陷阱表示法示例,都与实际CPU已经多年不做的事情不相符,因此我不会混淆您。一个好的陷阱表示法示例(也是您可能遇到的任何CPU上唯一符合硬件级别的陷阱表示法)是浮点类型中的信号NaN。即使IEC 60559详细规定了其行为,C99附录F(第2.1节)明确将信号NaN的行为留作未定义。

    值得一提的是,虽然指针类型是允许具有陷阱表示法的,但空指针不是陷阱表示法。只有当对它们进行解引用或偏移时,空指针才会导致未定义行为;其他操作(最重要的是比较和复制)则是被定义明确的。如果仅使用具有陷阱表示法的类型读取它们,则会导致未定义行为。 (无效但非空指针是否应视为陷阱表示法是争论的主题。CPU不会将它们视为这种方式,但编译器可能会。)

  2. 您展示的代码存在未定义行为,但这是由于指针别名规则而不是由于陷阱表示法。这是将一个float转换为具有相同表示形式的int的方法(假设,正如您所说,sizeof(float) == sizeof(int))。

int extract_int(float f)
{
    union { int i; float f; } u;
    u.f = f;
    return u.i;
}

在 C99 中,这段代码具有 未指定的(而不是未定义的)行为,这基本上意味着标准没有定义会产生什么整数值,但你会得到 一些 有效的整数值,它不是陷阱表达式,并且编译器不能假设您没有这样做来进行优化。(第6.2.6.1节,第7段。我所拥有的 C99 可能包含技术勘误——我的记忆是在最初的出版物中这个被视为未定义的,但在 TC 中被更改为未指定的。)


3
在C99标准中,确实存在未定义行为(UB)(参见附录J),这可能是疏忽所致(其他地方的措辞似乎表明情况不是这样)。在C1x中,它不再是未定义行为,并且措辞已经更加清晰。 - ninjalj
4
C99中此问题的缺陷报告/测试用例: www.open-std.org/jtc1/sc22/wg14/www/docs/dr_283.htmff - u0b34a0f6ae
6
IA64为整数类型提供了一个陷阱表示,称为“Not a Thing”(NaT)。更多信息请参见http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1208.htm和http://blogs.msdn.com/b/oldnewthing/archive/2004/01/19/60162.aspx。 - Adam Rosenfield
5
如果您仔细阅读您引用的缺陷报告,您会意识到ia64的NaT实际上不是符合C99标准的陷阱表示(DR要求更改以使其成为一个C99标准的陷阱表示,但据我所知,实际上从未发生过)。对于类型而言,C99标准的陷阱表示必须是适合该类型分配的可见空间的位模式;而NaTs则是超出边界的。这是NaT是糟糕的设计之一;您引用的Old New Thing博客还说明了另一种这样的方式。 - zwol
1
@supercat,我认为您刚刚描述了“int”陷阱表示的行为。 “unsigned char”本身不能具有陷阱表示,并且任何其他类型的陷阱表示都可以使用“具有字符类型的lvalue表达式”来读取。(C99 6.2.6.1p5) - zwol
显示剩余33条评论

6

将float类型与指向int的指针进行别名处理是未定义行为。


1
我同意这是UB,违反了严格别名规则。我之所以问这个问题,是因为我相信它在大多数编译器上都可以工作。请参见Chris Lutz在此处的答案:https://dev59.com/AkjSa4cB1Zd3GeqPFGg- - Burt
@Burt:那就标记编译器并在问题中指定它们。 - Puppy
4
@Burt: 将浮点数和整数进行别名化是未定义的行为,因为严格的别名规则,不能假设它在大多数编译器上都会“正常工作”。然而,char*可以别名化任何类型,这仅仅是实现定义的行为。或者,如果您使用的是GCC,可以使用__attribute__((may_alias)) - Joey Adams

5
一般来说,任何非陷阱IEEE-754浮点值都可以在某些平台上表示为整数,而不会出现任何问题。但是,有些浮点值可能会导致意外行为,如果您假设所有浮点值都具有唯一的整数表示并且您恰好强制FPU加载该值。
(此示例摘自Byte Swapping Floating Point Types。)
例如,当使用浮点数据在具有不同字节顺序的CPU之间进行编组时,您可能会考虑执行以下操作: double swap(double) 不幸的是,如果编译器将输入加载到FPU寄存器中并且它是陷阱表示,则FPU可以将其写回等效的陷阱表示,该表示恰好是不同的位表示。
换句话说,如果您没有通过正确的方式(正确的方式是通过unionchar *或其他标准机制进行memcpy)进行转换,则会有一些浮点值没有相应的位表示。

请求数字印章认证的REST API,点击此处查看文档:http://www.dmh2000.com/sampleapi/seal_api.htm我们需要一个POST请求,将JSON数据作为请求正文发送。请确保在请求标题中设置正确的内容类型("Content-Type")和授权令牌("Authorization")。您还需要使用正确的URL和端点。如果您有任何疑问,请参阅文档或联系API提供商获得帮助。 - Peter Mortensen

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