C++ 中未定义但正确的行为

问题描述 投票:0回答:2

下面的程序对 bit-fields、类型双关和对象表示进行了假设。换句话说,它不假装可移植性。不过,它的优点是。这个程序可以说是正确的,因为它需要一个受支持的编译器和一个具有正确终结性的平台吗?

#include <cassert>
#include <cstdint>

union Data {
    uint8_t raw[2];
    struct __attribute__((packed)) {
        unsigned int field1: 3, field2: 3, field3: 1, field4: 2;
        unsigned int field5: 7;
    } interpreted;
};


int main() {
    static_assert(sizeof(Data) == 2, "Struct size incorrect");
    static_assert(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__, "Only little-endian platform support is currently implemented");
    static_assert(
        #if defined(__GNUC__) || defined(__clang__)
                    true,
        #else
                    false,
        #endif
            "Compiler is neither GCC nor Clang"
    );
    Data d{.raw{0x69, 0x01}};
    /**
     *
     * 0x69  = 0b0110 1001, 0x01 = 0b0000 0001
     * On a little endian platform, each byte will be laid out in memory in reverse order, that is
     * [1001 0110, 1000 0000].
     * The GCC and clang compilers lay out the struct members in the order they are defined, and each of the values will be interpreted in the reverse order (little-endian), so
     * field1: 100 = 0b001
     * field2: 101 = 0b101
     * field3: 1   = 0b1
     * field4: 01  = 0b10
     * field5: 0000000 = 0
     *
     * Therefore, the following assertions will hold if the preceding assertions were satisfied.
     */
    assert(d.interpreted.field1 == 1);
    assert(d.interpreted.field2 == 5);
    assert(d.interpreted.field3 == 1);
    assert(d.interpreted.field4 == 2);
    assert(d.interpreted.field5 == 0);
}
c++ gcc clang undefined bit-fields
2个回答
0
投票

你的问题让我考虑如何使用 Ada 编程语言来表达它。以下示例按您希望的方式工作。

with Ada.Text_IO; use Ada.Text_IO;

procedure Main is
   type bit_3 is range 0 .. 2**3 - 1 with
      Size => 3;
   type bit_1 is range 0 .. 1 with
      Size => 1;
   type bit_2 is range 0 .. 3 with
      Size => 2;
   type bit_7 is range 0 .. 2**7 - 1 with
      Size => 7;

   type bit_fields is record
      field1 : bit_3;
      field2 : bit_3;
      field3 : bit_1;
      field4 : bit_2;
      field5 : bit_7;
   end record;

   for bit_fields use record
      field1 at 0 range 0 ..  2;
      field2 at 0 range 3 ..  5;
      field3 at 0 range 6 ..  6;
      field4 at 0 range 7 ..  8;
      field5 at 0 range 9 .. 15;
   end record;

   type unint_8 is range 0 .. 2**8 - 1 with
      Size => 8;

   package unint_io is new Integer_IO (unint_8);
   use unint_io;
   type two_bytes is array (Positive range 1 .. 2) of unint_8;

   foo : two_bytes;
   bar : bit_fields with
      Address => foo'Address;

begin
   foo := (16#69#, 1);
   for value of foo loop
      Put (Item => value, Base => 2);
      Put (" ");
   end loop;
   New_Line;

   Put_Line ("Field1 :" & bit_3'Image (bar.field1));
   Put_Line ("Field2 :" & bit_3'Image (bar.field2));
   Put_Line ("Field3 :" & bit_1'Image (bar.field3));
   Put_Line ("field4 :" & bit_2'Image (bar.field4));
   Put_Line ("field5 :" & bit_7'Image (bar.field5));
end Main;

我首先为位字段中需要的每个字段类型定义数据类型。因此,类型 bit_3 被定义为 3 位整数,bit_1 被定义为 1 位整数,bit_2 被定义为 2 位整数,bit_7 被定义为 7 位整数。

然后我创建了一个名为 bit_fields 的记录类型,指定了您在示例中指定的字段。记录定义子句之后的记录表示子句指定了记录的精确位布局,位编号从字节 0 开始。

然后我创建了一个 1 字节的整数类型,名为 unint_8,大小为 8 位。 我为 unint_8 实例化了一个通用 I/O 包,以便我可以以二进制形式输出该类型的值。 我创建了一个名为 two_bytes 的数组类型,其中包含两个 unint_8 元素。

Ada 本身没有联合。我通过创建一个名为 foo 的 two_bytes 实例和一个名为 bar 的 bit_fields 实例实现了相同的效果,并将 bar 的地址设置为与变量 foo 相同的地址。

我将十六进制值 69 和 1 分配给 foo 的两个元素,并以二进制形式打印 foo,然后以十进制形式打印 bar 的每个字段。

这个程序的输出是:

2#1101001# 2#1# 
Field1 : 1
Field2 : 5
Field3 : 1
field4 : 2
field5 : 0

0
投票

我不得不说,与我在这里得到的反馈相比,ChatGPT 知识更渊博,更有帮助,也更好用。以下是它给我的答案。

您提供的代码假定编译器是 GCC 或 Clang,并且平台是小端。它还假定位字段已打包并且

Data
联合的大小为 2 个字节。您添加的静态断言有助于确保满足这些条件。如果满足条件,则可以认为代码在给定的限制下是正确的。

但是,您还可以考虑其他几个方面:

  1. 位域的对齐和填充可以是特定于编译器的。虽然您已经检查了 GCC 或 Clang,但这些编译器的未来版本仍有可能改变它们的行为。您可以添加编译器版本检查以确保代码在特定范围的编译器版本下运行。

  2. 虽然代码会检查小端平台,但不会检查特定的体系结构。如果您想进一步限制代码可以运行的平台,您可能需要添加针对特定架构(例如 x86、ARM)的检查。

考虑到这几个方面,可以多加几个静态断言:

static_assert(
    #if (__GNUC__ > 0 && __GNUC__ < 12) || (__clang_major__ > 0 && __clang_major__ < 15)
        true,
    #else
        false,
    #endif
    "Compiler version not supported"
);

#if defined(__x86_64__) || defined(__i386__) || defined(__arm__)
    static_assert(true, "Supported architecture");
#else
    static_assert(false, "Unsupported architecture");
#endif

这些静态断言检查特定的编译器版本和支持的架构(x86,ARM)。根据您的具体要求调整版本号和架构列表。

使用额外的静态断言,如果平台、字节顺序、ABI 或编译器不满足要求,代码应该正确运行或不编译。

© www.soinside.com 2019 - 2024. All rights reserved.