Basics
Basing myself on Fizzer's answer, but with a more modern (C++17) style, and what I consider to be a nicer interface, allowing you to pad any struct/class in the following way:
#include "padded.h"
struct UnpaddedStruct
{
unsigned char unaligned_data[5];
};
using PaddedStruct = Padded<UnpaddedStruct>;
sizeof(UnpaddedStruct)
is 5, as expected.
sizeof(PaddedStruct)
is 8, as wanted.
The biggest difference here is that there's no special data members (aside from possibly padding), nor do you have to write your classes in any particular way to add padding. You simply use the Padded
struct for alignment, and otherwise access your struct in the exact same way you would without padding.
Here's a ready-made file you should be able to include as a header to allow
for that functionality:
#ifndef PADDED_H
#define PADDED_H
/**
* padded.h - Padding to align types with powers of two.
*/
#include <cstddef>
#include <cstdint>
#include <limits>
/**
* pad_size_of<T>() - Get the size required to align T to a power of two.
* @tparam T: The type to get alignment values for.
*
* Return: The amount of padding required to align T to a power of two.
*/
template <typename T>
constexpr size_t pad_size_of()
{
// Edge case. The highest number which is representable for any size type
// is going to be one less than the number we want to align it to.
const size_t max_of = std::numeric_limits<size_t>::max();
if constexpr(sizeof(T) > max_of / 2) {
return max_of - sizeof(T) + 1;
}
// We want to find the power of two that can fit AT LEAST sizeof(T).
size_t power_of_two = 1;
// There's definitely some better way to do this, but this is the most
// straight forward way to do what it's supposed to do. It's a constexpr
// function, so there should be no runtime overhead anyway.
while (power_of_two < sizeof(T)) {
power_of_two *= 2;
}
// Finally, our padding size is simply the difference between the two.
return power_of_two - sizeof(T);
}
/**
* struct Padded<T, Size> - A struct including padding for alignment.
* @tparam T: The class to add padding to.
* @tparam Size: The amount of padding to add to the class.
*
* Simple struct which can be inherited to add padding to any class. Will
* default to figuring out the amount of padding required to align a class to
* a power of two, then use that.
*/
template <typename T, size_t Size = pad_size_of<T>()>
struct Padded : public T
{
/// Simple array of bytes large enough to align the class to powers of two.
uint8_t padding[Size]{};
};
// Specialization of the above template for the base case, where no padding is
// required for alignment, so none is included.
template <typename T>
struct Padded<T, 0> : public T {};
#endif // PADDED_H
Details
For the especially interested, let's explain what's going on here.
First, I know some people dislike comments/docstrings finding them to be noisy, so let's start with the plain code with no comments:
#include <limits>
template <typename T>
constexpr size_t pad_size_of()
{
const size_t max_of = std::numeric_limits<size_t>::max();
if constexpr(sizeof(T) > max_of / 2) {
return max_of - sizeof(T) + 1;
}
size_t power_of_two = 1;
while (power_of_two < sizeof(T)) {
power_of_two *= 2;
}
return power_of_two - sizeof(T);
}
template <typename T, size_t Size = pad_size_of<T>()>
struct Padded : public T
{
uint8_t padding[Size]{};
};
template <typename T>
struct Padded<T, 0> : public T {};
The first bit of "magic" here is the constexpr
function:
template <typename T>
constexpr size_t pad_size_of()
constexpr
functions can be evaluated at compile-time, which means we can use them to figure out, e.g. class properties. We don't have to abuse structs and enums to do whatever, we simply write what we mean.
A constexpr
function call may be omitted by the compiler, instead using the result of the function call directly, rather than calling it at runtime. This means there should be no binary bloat or anything, it's just a simple function the compiler can use to figure out how much padding we need.
We then define our Padded
struct as a template class:
template <typename T, size_t Size = pad_size_of<T>()>
struct Padded : public T
It takes two template arguments, one being the class you want to add padding to, and the other being the amount of padding you want to add to it. It defaults to figuring that out automatically for you based on the size of the class, with the help of our pad_size_of
function, so that's usually not something you have to think about. You can think about it, if you want to e.g. align to a multiple of sizeof(int)
rather than a power of two, but power of two should be reasonable in most cases.
Finally, we have a specialization of our Padded
struct, namely:
template <typename T>
struct Padded<T, 0> : public T {};
This is the other "magical" bit. If a struct does not require padding, it will not have padding. Simple as. This allows you to e.g. add data members to the underlying struct without having to worry about updating any padding values or whatever. If no overhead is required, there will be no overhead.
Full Example
#include <cstdint>
#include <iostream>
#include "padded.h"
/**
* struct UnpaddedStruct - Some arbitrary struct with no padding.
*/
struct UnpaddedStruct
{
/// Whatever unaligned data you want.
uint8_t data[5];
};
/**
* struct Struct - An UnpaddedStruct with extra padding.
*/
using Struct = Padded<UnpaddedStruct>;
int main(int argc, char *argv[])
{
Struct s{'a', 'b', 'c', '\0'};
std::cout
<< "UNPADDED: " << sizeof(UnpaddedStruct)
<< ", PADDED: " << sizeof(Struct)
<< ", CONTENT: " << s.data
<< std::endl;
return 0;
}
Output
UNPADDED: 5, PADDED: 8, CONTENT: abc