20

Is there a way (trait or so) to detect, if struct/class has some padding?

I don't need cross-platform or standardized solution, I need it for MSVC2013.

I can check it like

namespace A
{
    struct Foo
    {
        int a;
        bool b;
    };
}

#pragma pack(push, 1)
namespace B
{
    struct Foo
    {
        int a;
        bool b;
    };
}
#pragma pack(pop)

static const bool has_padding = sizeof(A::Foo) != sizeof(B::Foo);

But C++ doesn't allow (as far as I know) generate this non-invasive (without touching existing structs)

Ideally I would like to get working something like this

template <typename T>
struct has_padding_impl
{
    typedef __declspec(align(1)) struct T AllignedT;
};

template <typename T>
struct has_padding : typename std::conditional<sizeof(typename has_padding_impl<T>::AllignedT) == sizeof(T),
                                               std::false_type,
                                               std::true_type>::type{};

EDIT - Why do I need this?

I'am working with existing serialization system, which store some struct just taking void* to them (inside generic function) and store sizeof(T) number of bytes... Such binary file is not portable on platforms we are targeting, since different compilers are used, so there is no guarantee how is padding inserted. If I could statically detect all T which are structs with padding, I can force user to manually insert padding (some control padding e.g. not just random garbage) so there is no "random" padding. Another adventage is, when I diff two save files of same scenerio, they will look the same.

edit 2 the more I think about it, the more I realize I need cross-platform solution. We mainly develop on msvc2013 but our application is at final builded in msvc2012 and clang. But if I detected and get rid of all compiler-generated padding in msvc2013, there is no guarantee that other compiler doesn't insert padding... (so msvc2013 detection is not enough)

relaxxx
  • 7,566
  • 8
  • 37
  • 64
  • 4
    Why do you think you need this? – Lightness Races in Orbit Aug 14 '15 at 10:02
  • 1
    Padding behaves much like an unnamed member. Since you can't enumerate members, it's impossible to distinguish between normal members and those "unnamed members" acting as padding. – MSalters Aug 14 '15 at 10:10
  • @JeroenBaert: That's just one of the sources of the variation which he's trying to detect. As the example code shows, it can also be defined on a class-by-class basis. – MSalters Aug 14 '15 at 10:14
  • @LightnessRacesinOrbit see edit – relaxxx Aug 14 '15 at 12:43
  • @relaxxx: Typically one achieves this by either serialising in a padding-agnostic way (i.e. member-by-member), or enforcing zero-padding with compiler intrinsics. I don't see the value in knowing what the implementation-defined padding is: you want to get rid of it, not know it! – Lightness Races in Orbit Aug 14 '15 at 13:20
  • there are actually two things I'm talking about. zero-padding (padding present, but not random garbage but zeros) ale less important but "nice to have". Other thing is, to save/load chunks on application produced by different compilers, so `sizeof` on the same struct doesn't have to give the same results. or padding could be placed differently. So I want to force user to manually put padding where some compiler would. – relaxxx Aug 14 '15 at 13:27
  • Clang has `-Wpadded`, maybe it can be made useful for your case. – geza Nov 05 '18 at 14:53
  • 3
    in many cases you can use `std::has_unique_object_representations`. See [Compile-time check to make sure that there is no padding anywhere in a struct](https://stackoverflow.com/q/57829861/995714) – phuclv Sep 18 '19 at 02:05

5 Answers5

4

The closest thing, available in C++17, is std::has_unique_object_representations. It is basically the boolean inverse of your suggested has_padding<T> structure, but it also fails for floating point types.

In my code base, I used a combination of std::has_unique_object_representations_v<T> || std::is_same_v<T, float> || std::is_same_v<T, double> to check what I needed, but this won't cover if a float is a member variable, for example.

You might need to make your own has_padding implementation structure which has a generic implementation that defers to std::has_unique_object_representations<T>, where a custom type that contains a float or double member can add a specialisation after you've manually checked it's okay:

#include <type_traits>

template <typename T>
struct has_padding : std::conditional<std::has_unique_object_representations<T>::value || std::is_same<T, float>::value || std::is_same<T, double>::value,
                                      std::false_type,
                                      std::true_type>::type{};

struct MyType
{
    float a;
    float b;
};
// I've checked it manually and decided MyType is okay:
template<>
struct has_padding<MyType> : std::false_type{};

It's not perfect but it's the closest you would get to an automatic check for structure padding in C++ as far as I'm aware.

Timotheos
  • 405
  • 3
  • 8
2

Do you need this information during run time? Because if you want to know it in build time I believe you can use static_assert to get this information.

struct foo
{
    uint64_t x;
    uint8_t y;
};
#define EXPECTED_FOO_SIZE (sizeof(uint64_t) + sizeof(uint8_t))
static_assert(sizeof(foo) == EXPECTED_FOO_SIZE, "Using padding!");

If you need it during run time, you can try something like:

static const bool has_padding = (sizeof(foo) != EXPECTED_FOO_SIZE);

Also check this link from earlier post, maybe it will help.

Alex Lop.
  • 6,810
  • 1
  • 26
  • 45
  • 5
    Thank you, unfortunately I need generic solution, it is not possible to enumerate all struct members of all struct by hand – relaxxx Aug 14 '15 at 12:44
  • Like @relaxxx has said, I run in the same problem and I don't see a simple solution without enumerate all of the types independently. Do you know a more elegant and generic solution? – LXSoft Nov 05 '18 at 10:32
  • 1
    I think this should be transformed to static const bool has_padding = (sizeof(foo) != EXPECTED_FOO_SIZE); Or static const bool has_padding = !(sizeof(foo) == EXPECTED_FOO_SIZE); The padding will be present only if there is a difference in the size if is equal no padding. Not-ing the result or the test make the truly sense for the variable in upper instance. – LXSoft Nov 05 '18 at 13:50
2

As of C++20, there are finally (almost) enough tools available to detect padding in a generic way - at least for trivially copyable objects. The general idea is to build the object representation of the type, bit_cast<>() it to the type of interest, and value-compare it against a reference type. If you perturb each byte in the object representation and all value comparisons return unequal, then there shouldn't be any padding bytes.

#include <array>
#include <bit>
#include <cstdint>

template <typename T>
consteval bool HasPadding() {
  auto bytes = std::array<uint8_t, sizeof(T)>{};
  const T reference = std::bit_cast<T>(std::array<uint8_t, sizeof(T)>{});

  for (uint32_t i = 0; i < sizeof(T); ++i) {
    bytes[i] = 1u;  // Perturb the object representation.
    const T instance = std::bit_cast<T>(bytes);
    if (instance == reference) {
      return true;
    }
    bytes[i] = 0u;  // Restore the object representation.
  }
  return false;
}

The catch with this is that a value-comparing == operator (or similar) needs to be defined in the type of interest. This isn't too much of an ask; the spaceship operator makes this a one-liner:

auto operator<=>(const Type&) const = default;

...but it does force you to modify the underlying type (which is why I'm still not aware of a fully generic solution).

As for the motivation behind your question, the edits are headed in the right direction. Serialization and deserialization of messages is a common problem and there are numerous frameworks and protocols for doing this. They generally involve arranging data in a platform agnostic format that can then be unpacked by a different platform, but choosing a protocol is a huge topic and outside of the scope of this answer. However, whether or not a type has padding bytes will sometimes come up in the context of generating the type with random data in unit tests, so determining whether something has padding bytes remains relevant.

user2465116
  • 388
  • 2
  • 10
1

Try out this macro :

#define TO_STR(str) #str
#define DECL_STRUCT_TEST_ALIGNED(structName, test_alignment, body) \
_Pragma(TO_STR(pack(push,test_alignment)))\
struct test_##structName \
body ; \
_Pragma(TO_STR(pack(pop))) \
struct structName \
body; \
static const bool has_padding_##structName = sizeof(test_##structName)!=sizeof(structName);

DECL_STRUCT_TEST_ALIGNED(bar, 1,
{
                         int a;
                         bool b;
                     }
                     )


DECL_STRUCT_TEST_ALIGNED(foo,1,
{
                         int a;
                         int b;
                     })

And now, at runtime you can test :

if (has_padding_foo)
{
    printf("foo has padding\n");
} else {
    printf("foo doesn't have padding\n");
}
if (has_padding_bar)
{
    printf("bar has padding\n");
} else {
    printf("bar has no padding\n");
}

And ofc, you can use static_assert if you want to get error at compile time.

MichaelCMS
  • 4,703
  • 2
  • 23
  • 29
  • Thank you, but I don't see how this will work without touching existing struct, I need `trait`. Please, see my question – relaxxx Aug 14 '15 at 12:47
  • well, you don't need to modify it's content, you don't need to change it's pack , you however need to declare it using the above macro. The structure is not touched. – MichaelCMS Aug 14 '15 at 12:55
  • By not touch, you mean you cannot modify the file where the structure is declared ? – MichaelCMS Aug 14 '15 at 12:56
  • Yes. I don't want to (and can't) change all structs in our codebase and force others to write structs like that, see http://www.cplusplus.com/reference/type_traits/ for examples – relaxxx Aug 14 '15 at 13:08
  • Then you can't do it. There's no way you can do this with TMP traits. – Lightness Races in Orbit Aug 14 '15 at 13:21
-1

May be you should try something like this:

#include <iostream>
using namespace std;

struct A
{
    int a;
    bool b;
};

int main(int argc, char *argv[])

{
    A foo;

    cout << "sizeof struct = " << sizeof(A) << endl;
    cout << "sizeof items  = " << sizeof(foo.a) + sizeof(foo.b) << endl;
    return 0;
}

I got:

sizeof struct = 8
sizeof items  = 5

I am on Ubuntu 14.04.

Richard Rublev
  • 7,718
  • 16
  • 77
  • 121
  • 1
    This does not take into account extra space that is not padding (like a vPtr), and requires enumerating the members by hand. – Quentin Aug 14 '15 at 10:08
  • Thank you, unfortunately I need generic solution, it is not possible to enumerate all struct members of all struct by hand – relaxxx Aug 14 '15 at 12:48