Использование уже определенной структуры в качестве анонимного члена объединения - PullRequest
0 голосов
/ 03 июня 2019

Допустим, у меня есть 32-битный аппаратный регистр Reg, к которому я хочу иметь доступ как в виде 32-битного значения (например, Reg = 0x12345678), так и в виде битовых полей (например, Reg.lsw = 0xABCD). Я могу добиться этого, объявив объединение с анонимным членом структуры и объявив операторы присваивания и преобразования в / из uint32_t. В среде с прямым порядком байтов код может выглядеть следующим образом:

#include <cstdint>
#include <cstdio>

typedef union
  {
  uint32_t val ;
  struct
    {
    uint32_t lsw : 16 ;
    uint32_t msw : 16 ;
    } ;

  operator = (uint32_t n) { val = n ; }
  operator uint32_t() const { return val ; }
  } HWR ;

int main()
  {
  HWR Reg ;
  Reg = 0x12345678 ;
  Reg.lsw = 0xABCD ;
  printf ("%X\n", uint32_t(Reg)) ;
  }

Но теперь давайте представим, что у меня есть целая куча этих регистров, каждый со своим собственным макетом битового поля, и у меня есть файл заголовка FieldDefs.h, который объявляет эти макеты битового поля как именованные структуры. Как я могу использовать эти именованные структуры в приведенном выше коде, чтобы я мог получить доступ к 32-битному значению, а также к отдельным битовым полям? Я мог бы сделать это так:

#include "FieldDefs.h" // Defines struct MyHWR
typedef union
  {
  uint32_t val ;
  struct MyHWR field ;

  operator = (uint32_t n) { val = n ; }
  operator uint32_t() const { return val ; }
  } MyHWRUnion ;

Но теперь вместо Reg.lsw =... мне нужно набрать Reg.field.lsw =...

Есть ли способ (в C ++ 17) объявить уже определенную структуру как анонимный член объединения? Я использую g ++ версии 7.3.0, если это имеет значение.

Ответы [ 2 ]

2 голосов
/ 03 июня 2019
union
{
// ...
    struct
    {
    // ...
    };

Это анонимная структура.Анонимные структуры плохо сформированы в C ++.Только союзы могут быть анонимными.Это отличается от C, где допускаются анонимные структуры (начиная с C11).

Есть ли способ (в C ++ 17) объявить уже определенную структуру как анонимный член объединения?

Нет.Неназванные члены не могут иметь именованный тип.

Вам нужно будет сделать выбор между неназванным членом и предварительно объявленным классом.Учитывая, что анонимная структура в первую очередь нестандартна, я рекомендую использовать именованный член и предопределенный класс.Может быть, дать ему короткое имя, чтобы минимизировать многословие.

1 голос
/ 03 июня 2019

Полагаю, никому не понравится этот ответ, ни OP (поскольку требует g++ 9.1), ни гуру C ++ (пахнет UB?), Но я все еще немного горжусь тем, что возиться с ним.

Есть *Атрибут 1004 *[[no_unique_address]] входит в C ++ 20, а g ++ 9.1 уже поддерживает его (даже без флага -std=c++2a).

Как его можно использовать здесь? При тестировании и испытаниях кажется, что если мы создадим прокси-член val, помеченный им, он получит адрес объекта 1 .

Таким образом, мы можем создать Proxy класс с operator=(uint32_t) и operator uint32_t, который обрабатывает this как uint32_t.Прокси-объект не имеет адреса, не увеличивает размер структуры, которая его использует.

Имена битовых полей должны быть добавлены наследованием, заключенным в простой шаблон, для согласованности с именем HWR.

Вуаля, у нас есть HWR<bitfield> объект, который может быть назначен uint32_t напрямую, val членом и дает доступ к именам битовых полей.

https://godbolt.org/z/N2xEmz

#include <bits/stdint-uintn.h>

#include <cstddef>
#include <cstdint>
#include <cstdio>

// Example bifields, I assumed you have such in "FieldDefs.h"
struct bitfield {
  uint32_t lsw : 16;
  uint32_t msw : 16;
};

struct ThisProxy {
  uint32_t& operator=(uint32_t n) {
    auto& uint = *reinterpret_cast<uint32_t*>(this);
    uint = n;
    return uint;
  }
  operator uint32_t() const { return *reinterpret_cast<const uint32_t*>(this); }
};

template <typename Bitfield>
struct HWR : Bitfield {
  static_assert(sizeof(Bitfield) == 4, "Bad things would happen");
  HWR& operator=(uint32_t n) {
    this->val = n;
    return *this;
  }
  operator uint32_t() const { return this->val; }
  [[no_unique_address]] ThisProxy val;
};

int main() {
  HWR<bitfield> Reg;
  // Sanity check that proxy points at &Reg and does not increase size
  static_assert(offsetof(HWR<bitfield>, val) == 0, "");
  static_assert(sizeof(HWR<bitfield>) == 4, "");

  Reg = 0x12345678;
  Reg.val = 0x8765432A;
  Reg.lsw = 0xABCA;
  printf("%X\n%ld\n", uint32_t(Reg), sizeof(Reg));

  return 0;
}

Изменить:

Как оказалось, доступ по Reg.val не является обязательным, трюк с наследованием + reinterpret_cast может быть повторно использован в коде до C ++ 20.

template <typename Bitfield> struct HWR : Bitfield {
  static_assert(sizeof(Bitfield) == 4, "Bad things would happen");
  HWR &operator=(uint32_t n) {
    *reinterpret_cast<uint32_t *>(this) = n;
    return *this;
  }
  operator uint32_t() const {
    return *reinterpret_cast<const uint32_t *>(this);
  }
};

Там все еще пахнет reinterpret_cast, и мне нужно найти одну вещь, чтобы полностью рекомендовать этот код.Всякий раз, когда битовое поле может быть интерпретировано базовым типом uint32_t.

1 Я не уверен, когда смещение 0 гарантируется P0840R2 .

PS.g ++ жалуется с warning: offsetof within non-standard-layout type ‘HWR<bitfield>’ is conditionally-supported [-Winvalid-offsetof], но я не пытался найти обходной путь для этого.

PPS.Нет анонимных структур!

...