Approach for automatic fields reordering in C-like

2020-06-18 10:23发布

问题:

Is there a way to perform automatic fields reordering in C-like structs? I mean the using of the language features like( preprocessor for C and C++ and templates/type traits/etc for C++), which make it possible to do the following macro (Boost.Fusion-like style to adapt structures):

REARRANGE(StructureName,
          (int8_t)(FieldName1),
          (int32_t)(FieldName2),
          (int16_t)(FieldName3),
          (int32_t)(FieldName4));
// is equivalent to (without loss of generality):
struct StructureName
{

    int32_t FieldName2;
    int32_t FieldName4;
    int16_t FieldName3;
    int8_t FieldName1;

};

Of course, approach should take into account alignof values (together with sizeof) for fields and, if it possible, #pragma pack current value.

I am aware of bad portability of the result, but it for local use only.

It is mandatory thing to save the field names together with respective types.

The aim is to reduce total structure size.

回答1:

I found the C++14 solution:

#include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/repetition/enum.hpp>

#include <utility>

#include <cstddef>

namespace details
{

template< std::size_t /*index*/, typename /*tag*/ >
struct member;

struct pair
{
    std::size_t k, v; 
    constexpr bool operator < (pair const & r) const { return r.k < k; }
};

constexpr void swap(pair & l, pair & r) { pair m = r; r = l; l = m; }

template< int N >
constexpr
void qsort(pair (&a)[N], int const l, int const r)
{ 
    int i = l, j = r;
    pair pivot = a[l + (r - l) / 2];
    while (!(j < i)) { 
        while (a[i] < pivot) ++i;
        while (pivot < a[j]) --j;
        if (!(j < i)) {
            swap(a[i], a[j]);
            ++i;
            --j;
        }
    }
    if (l < j) qsort(a, l, j);
    if (i < r) qsort(a, i, r);
}

template< int N >
struct map
{
    pair a[N];
};

template< int N, std::size_t ...indices >
constexpr
map< N > make_map(pair (&a)[N], std::index_sequence< indices... >)
{
    return {{a[indices]...}};
}

template< int N >
constexpr
map< N > qsort(pair (&&a)[N])
{
    if (1 < N) {
        qsort< N >(a, 0, N - 1);
    }
    return make_map< N >(a, std::make_index_sequence< N >{});
}

}

#define GEN0(z, tag, index, type_name) template<> struct member< index, tag > \
{ BOOST_PP_SEQ_HEAD(type_name) BOOST_PP_SEQ_HEAD(BOOST_PP_SEQ_TAIL(type_name)); };

#define GEN1(z, ignored, index, type_name) {sizeof(BOOST_PP_SEQ_HEAD(type_name)), index},

#define GEN2(z, index, tags) details::member< BOOST_PP_SEQ_HEAD(tags)::map.a[index].v, BOOST_PP_SEQ_HEAD(BOOST_PP_SEQ_TAIL(tags)) >

#define GEN(ns, tag, members) \
namespace ns { struct tag; } \
namespace details { BOOST_PP_SEQ_FOR_EACH_I(GEN0, ns::tag, members) } \
namespace details::tags::ns { struct tag { static constexpr auto map = qsort({BOOST_PP_SEQ_FOR_EACH_I(GEN1, %%, members)}); }; } \
namespace ns { struct tag : BOOST_PP_ENUM(BOOST_PP_SEQ_SIZE(members), GEN2, (details::tags::ns::tag)(tag)) {}; }

struct T { char c[3]; };

GEN(user::u2, S, ((char)(c))((int)(i))((T)(t)))

#include <cassert>

int main()
{
    using namespace details;
    void(member< 0, user::u2::S >{}.c);
    void(member< 1, user::u2::S >{}.i);
    static_assert(tags::user::u2::S::map.a[0].k == 4);
    static_assert(tags::user::u2::S::map.a[1].k == 3);
    static_assert(tags::user::u2::S::map.a[2].k == 1);
    user::u2::S s{4, {'a', 'b', 'c'}, 'd'};
    assert((void *)&s.i == (void *)&s);
    assert((void *)&s.t < (void *)&s.c);
    static_assert(sizeof(s) == 8);
}


回答2:

Some solutions:

  • Reorder the fields with clang-reorder-fields in clang-tools-extra
  • Let gcc do that automatically using -fipa-struct-reorg. Since that option has been removed, currently if you want to try you'll have to use the struct-reorg branch on gcc SVN or the github mirror

Related:

  • Automated field re-ordering in C structs to avoid padding
  • Struct Reordering by compiler


回答3:

  • For MS compiler use #pragma pack, (#pragma pack(1) eliminates all gaps). And check the link for details, as this directive is not guaranteed to work always.
  • For GCC there is __attribute__ ((packed))

As your only goal is the minimal possible size of the data in memory, this is exactly what you need.