C99中的别名,类型 - 双关,联合,结构和位域

时间:2014-07-03 00:02:39

标签: c c99 language-lawyer

在回答this question后收到以下声明:

  
    

...您正在尝试重叠valuebits,并将数据填充到联合的一个替代项中,并将其从另一个中取出 undefined 。< / p>   

对于C99中的打字类型,我对于允许(以及谨慎)的内容变得更加好奇。在浏览一下后,我在帖子Is type-punning through a union unspecified in C99...中找到了很多有用的信息。

在那里发表的评论和答案都有很多内容。为了清晰起见(以及作为一个完整性检查),我想根据我对C99标准的理解创建一个示例。下面是我创建的示例代码,虽然它按照我的预期运行,但我想确定我的断言是正确的。

以下代码在评论中包含我的断言。这是我对C99中类型惩罚的理解。我的评论是否正确?如果没有,你能解释一下原因吗?

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#define NUM_BYTES   sizeof(uint32_t)
typedef union
{
    uint32_t fourByteValue;
    uint8_t  byteValue[NUM_BYTES];
    struct
    {
        unsigned int firstBitSpecified  :   1;
        unsigned int secondBitSpecified :   1;
        unsigned int thirdBitSpecified  :   1;
        unsigned int fourthBitSpecified :   1;
        unsigned int paddingBits        :   4;
        uint8_t  oneByteStructValue;
        uint16_t twoByteStructValue;
    };
} U;

int main (void)
{
    const char border[] = "==============================\n";
    U myUnion;
    uint8_t byte;
    uint32_t fourBytes;
    uint8_t i;

    myUnion.fourByteValue = 0x00FFFFFF;
    fourBytes = myUnion.fourByteValue;  /* 1. This is not type-punning. */
    printf("No type-punning fourByteValue:\n%s"
           "fourBytes\t= 0x%.4x\n\n", border, fourBytes);


    printf("Type-punning byteValue:\n%s", border);
    for (i = 0; i < NUM_BYTES; i++)
    {
        byte = myUnion.byteValue[i];   /* 2. Type-punning allowed by C99, 
                                             no unspecified values. */
        printf ("byte[%d]\t\t= 0x%.2x\n", i, byte);
    }
    printf("\n");

    myUnion.byteValue[3] = 0xff;
    fourBytes = myUnion.fourByteValue; /* 3. Type-punning allowed by C99 
                                             but all other 'byteValue's
                                             are now unspecified values. */
    printf("Type-punning fourByteValue:\n%s"
           "fourBytes\t= 0x%.4x\n\n", border, fourBytes);

    myUnion.firstBitSpecified = 0;
    myUnion.thirdBitSpecified = 0;
    fourBytes = myUnion.fourByteValue; /* 4. Again, this would be allowed, but 
                                             the bit that was just assigned
                                             a value of 0 is implementation
                                             defined AND all other bits are
                                             unspecified values. */
    printf("Type-punning firstBitSpecified:\n%s"
           "fourBytes\t= 0x%.4x\n\n", border, fourBytes);

    myUnion.fourByteValue = 0x00000001;
    fourBytes = myUnion.firstBitSpecified; /* 5. Type-punning allowed, although
                                                 which bit you get is implementation
                                                 specific. */
    printf("No type-punning, firstBitSpecified:\n%s"
           "fourBytes\t= 0x%.4x\n\n", border, fourBytes);
    fourBytes = myUnion.secondBitSpecified;
    printf("No type-punning, secondBitSpecified:\n%s"
           "fourBytes\t= 0x%.4x\n\n", border, fourBytes);

    return (EXIT_SUCCESS);
}

以上代码是在64位Windows 7计算机上使用mingw32-gcc.exe -Wall -g -std=c99编译的。运行代码后,我收到以下输出:

No type-punning fourByteValue:
==============================
fourBytes       = 0xffffff

Type-punning byteValue:
==============================
byte[0]         = 0xff
byte[1]         = 0xff
byte[2]         = 0xff
byte[3]         = 0x00

Type-punning fourByteValue:
==============================
fourBytes       = 0xffffffff

Type-punning firstBitSpecified:
==============================
fourBytes       = 0xfffffffa

No type-punning, firstBitSpecified:
==============================
fourBytes       = 0x0001

No type-punning, secondBitSpecified:
==============================
fourBytes       = 0x0000

1 个答案:

答案 0 :(得分:3)

我在阅读该帖子中的脚注时,通过联合打字是从不指定。从this开始,标准说:

  

有一个例外,如果在一个之后访问一个union对象的成员   值已存储在对象的不同成员中   行为是实现定义的。

脚注不会改变这一点。这种情况的原因是C不保证(a)数字类型的字节顺序,或(b)struct成员的内存排序,除非第一个成员必须字节对齐&#34;开头&#34; struct(这样你可以做他们在GTK中做的那种转换来实现多态)。

有问题的脚注涉及这一行:

  

当一个值存储在union类型的对象的成员中时,   对象表示的字节与该对应的字节不对应   成员,但确实对应其他成员采取未指定的值,但   联合对象的值不应因此成为陷阱   表示

它说:

  

78a如果用于访问union对象内容的成员不是   与上次用于在对象中存储值的成员相同,   值的对象表示的适当部分是   如上所述,重新解释为新类型中的对象表示   在6.2.6(一个过程有时被称为&#34;类型双关语&#34;)。 这可能是一个   陷阱表示。

&#34;重新解释为新类型中的对象表示&#34;是实现定义的(因为所有类型的解释,在逐字节级别,始终是实现定义的,考虑到诸如字节序等的事情)。脚注只是添加了更多细节,指出当您通过联合处理类型系统时可能会发生令人惊讶的事情,包括导致陷阱表示。查看here以获取&#34;陷阱表示的定义:

  

陷阱表示是一组位,当被解释为a时   特定类型的值,会导致未定义的行为。陷阱   表示最常见于浮点和指针   价值,但从理论上讲,几乎任何类型都可能有陷阱   表示。未初始化的对象可能会占用陷阱   表示。这提供了与旧规则相同的行为:访问   未初始化的对象会产生未定义的行为。

     

标准对访问未初始化的唯一保证   数据是 unsigned char类型没有陷阱表示,和   填充没有陷阱表示。

因此,通过在帖子中用uint_8替换unsigned char,您可以避免未定义的行为,并最终得到特定于实现的行为。但是,正如现在所写,标准不禁止UB。

在您关联的帖子的引文中明确说明了这一点:

  

最后,从C90到C99的一个变化是删除任何   在最后一家商店的时候限制访问工会的一个成员   到另一个。 理由是这种行为会随之而来   取决于值的表示。

根据定义,基础表示从未被标准定义。