演员阵容和工会一样安全吗?

时间:2014-05-01 19:39:20

标签: c++ c arduino embedded unions

我想将像浮点数这样的大变量分成字节段,并通过UART逐字节地发送这些变量。我正在使用C / C ++。

一种方法可能是深入复制我想要发送给联合的值,然后发送它。我认为这将是100%安全但速度慢。工会看起来像这样:

   union mySendUnion
   {
       mySendType sendVal;
       char[sizeof(mySendType)] sendArray; 
    }

另一种选择可能是将指向我想要发送的值的指针转换为指向特定联合的指针。这还安全吗?

第三个选项可能是将指针强制转换为我想要发送给char的值,然后递增一个像这样的指针:

            sendType myValue = 443.2;

    char* sendChar = (char*)myValue; 

    for(char i=0; i< sizeof(sendType) ; i++)
    {
        Serial.write(*(sendChar+j), 1);
    }

我已经成功使用了上述指针算术,但我不确定它在所有情况下是否安全。我担心的是,如果我们使用32位处理器并想发送一个浮点数会怎样。编译器选择将此32位浮点存储到一个存储器单元中,但仅将一个单个字符存储到每个32位单元中。

然后每个计数器增量使程序指针增加一个整个存储单元,我们就会错过浮点数。

C标准中是否存在阻止此问题的内容,或者这可能是某个编译器的问题?

3 个答案:

答案 0 :(得分:6)

首先,你不能用“C / C ++”编写代码。没有“C / C ++”这样的语言,因为它们基本上是不同的语言。因此,关于工会的答案根本不同。

关于标题:

  

演员阵容是否像工会一样安全?

不,通常他们不是,因为strict aliasing rule。也就是说,如果使用指向不兼容类型的指针键入某个特定类型的指针,则会导致未定义的行为。 此规则的唯一例外是,当您通过指向(有符号或无符号)char的指针对对象进行别名来读取或操作对象的逐字节表示时。与您的情况一样。

然而,工会是完全不同的混蛋。在C99及更高版本中允许通过复制到联合并从联合读取来输入惩罚,但在C89和所有版本的C ++中都会导致未定义的行为。

一个方向中,如果原始联合作为实际对象,则还可以使用指向union的指针安全地输入pun(在C99及更高版本中)。像这样:

union p {
    char c[sizeof(float)];
    float f;
} pun;
union p *punPtr = &pun;

punPtr->f = 3.14;
send_bytes(punPtr->c, sizeof(float));

因为“指向联合的指针指向其所有成员,反之亦然”(C99,我不记得确切的pargraph,它大约是6.2.5,IIRC)。 但在另一个方向却不是这样:

float f = 3.14;
union p *punPtr = &f;
send_bytes(punPtr->c, sizeof(float)); // triggers UB!

总结一下:以下代码片段在C89,C99,C11和C ++中均有效:

float f = 3.14;
char *p = (char *)&f;
size_t i;
for (i = 0; i < sizeof f; i++) {
    send_byte(p[i]); // hypotetical function
}

以下内容仅在C99及更高版本中有效:

union {
    char c[sizeof(float)];
    float f;
} pun;

pun.f = 3.14;
send_bytes(pun.c, sizeof float); // another hypotetical function

然而,以下有效:

float f = 3.14;
unsigned *u = (unsigned *)&f;
printf("%u\n", *u); // undefined behavior triggered!

始终保证可以使用的另一种解决方案memcpy()memcpy()函数在两个对象之间进行逐字节复制。 (不要让我开始“缓慢” - 在大多数现代编译器和stdlib实现中,它是一个内在函数)。

答案 1 :(得分:1)

在字节流上发送浮点数据时的一般建议是使用某种serialization技术,以确保数据格式定义良好(并且最好是架构中立,谨防endianness问题!)。

您可以使用XDR - 或许ASN1 - 这是一种二进制格式(有关详情,请参阅xdr(3))。对于C ++,另请参阅libs11n

除非速度或数据大小非常关键,否则我建议使用JSONYAML等文本格式(文本格式更详细,但更容易调试和记录)。有几个很好的库支持它(例如C {的jsoncpp或C的jansson

请注意,串口很慢(w.r.t.CPU)。因此序列化处理时间可以忽略不计。

无论您做什么,请记录序列化格式(即使是内部项目)。

答案 2 :(得分:0)

[[un]signed] char [const] *投射是合法的,在阅读时不会导致问题,所以这是一个不错的选择(也就是说,在修复char *sendChar = reinterpret_cast<char*>(&myValue);之后,因为你在它,所以把它const

现在下一个问题出现在另一边,在阅读时,因为你不能安全地使用相同的方法进行阅读。一般来说,复制变量的成本远低于通过UART发送的成本,因此我只需在读出串口时使用union