编译时检查以确保结构中的任何地方都没有填充

Compile-time check to make sure that there is no padding anywhere in a struct

有没有办法编写一个编译时断言来检查某些类型是否有任何填充?

例如:

struct This_Should_Succeed
{
    int a;
    int b;
    int c;
};

struct This_Should_Fail
{
    int a;
    char b;
    // because there are 3 bytes of padding here
    int c;
};

编辑:选中

Is there a way to write a compile-time assertion that checks if some type has any padding in it?

是的。

您可以对所有成员的 sizeof 求和并将其与 class 本身的大小进行比较:

static_assert(sizeof(This_Should_Succeed) == sizeof(This_Should_Succeed::a)
                                           + sizeof(This_Should_Succeed::b)
                                           + sizeof(This_Should_Succeed::c));

static_assert(sizeof(This_Should_Fail)    != sizeof(This_Should_Fail::a)
                                           + sizeof(This_Should_Fail::b)
                                           + sizeof(This_Should_Fail::c));

不幸的是,这需要为求和明确命名成员。自动解决方案需要(编译时)反射。不幸的是,C++ 语言还没有这样的特性。如果我们幸运的话,也许在 C++23 中。目前,有基于将 class 定义包装在宏中的解决方案。

一个不可移植的解决方案可能是使用 GCC 提供的 -Wpadded 选项,如果结构包含任何填充,它承诺会发出警告。这可以与 #pragma GCC diagnostic push 结合使用,只对选定的结构执行此操作。


type I'm checking, the type is a template input.

一种可移植但不完全令人满意的方法可能是使用自定义特征,模板的用户可以使用该特征来自愿承诺该类型不包含填充,从而使您可以利用知识。

用户将不得不依赖显式或基于预处理器的断言来证明他们的承诺是正确的。

从 C++17 开始,您可能 能够使用 std::has_unique_object_representations.

#include <type_traits>

static_assert(std::has_unique_object_representations_v<This_Should_Succeed>); // succeeds
static_assert(std::has_unique_object_representations_v<This_Should_Fail>); // fails

尽管如此,这可能无法完全按照您的要求执行。查看链接的 cppreference 页面了解详细信息。

要在不重新键入每个结构成员的情况下获得总字段大小,您可以使用 X Macro

首先定义所有字段

#define LIST_OF_FIELDS_OF_This_Should_Fail    \
    X(int, a)          \
    X(char, b)         \
    X(int, c)

#define LIST_OF_FIELDS_OF_This_Should_Succeed \
    X(long long, a)    \
    X(long long, b)    \
    X(int, c)          \
    X(int, d)          \
    X(int, e)          \
    X(int, f)

然后声明结构

struct This_Should_Fail {
#define X(type, name) type name;
    LIST_OF_FIELDS_OF_This_Should_Fail
#undef X
};

struct This_Should_Succeed {
#define X(type, name) type name;
    LIST_OF_FIELDS_OF_This_Should_Succeed
#undef X
};

并检查

#define X(type, name) sizeof(This_Should_Fail::name) +
static_assert(sizeof(This_Should_Fail) == LIST_OF_FIELDS_OF_This_Should_Fail 0);
#undef X

#define X(type, name) sizeof(This_Should_Succeed::name) +
static_assert(sizeof(This_Should_Succeed) == LIST_OF_FIELDS_OF_This_Should_Succeed 0);
#undef X

或者您可以重复使用相同的 X 宏来检查

#define X(type, name) sizeof(a.name) +
{
    This_Should_Fail a;
    static_assert(sizeof(This_Should_Fail) == LIST_OF_FIELDS_OF_This_Should_Fail 0);
}
{
    This_Should_Succeed a;
    static_assert(sizeof(This_Should_Succeed) == LIST_OF_FIELDS_OF_This_Should_Succeed 0);
}        
#undef X

demo on compiler explorer

有关这方面的更多信息,您可以阅读 Real-world use of X-Macros

另一种 non-portable 解决方案是将结构的大小与 packed 版本与 #pragma pack or __attribute__((packed)). #pragma pack is also supported by many other compilers like GCC or IBM XL

进行比较
#ifdef _MSC_VER
#define PACKED_STRUCT(declaration) __pragma(pack(push, 1)) declaration __pragma(pack(pop))
#else
#define PACKED_STRUCT(declaration) declaration __attribute((packed))
#endif

#define THIS_SHOULD_FAIL(name) struct name \
{                        \
    int a;               \
    char b;              \
    int c;               \
}

PACKED_STRUCT(THIS_SHOULD_FAIL(This_Should_Fail_Packed));
THIS_SHOULD_FAIL(This_Should_Fail);

static_assert(sizeof(This_Should_Fail_Packed) == sizeof(This_Should_Fail));

Demo on Compiler Explorer

Force C++ structure to pack tightly. If you want to have an even more portable pack macro then try this

相关:


GCC and Clang 中有一个用于此目的的 -Wpadded 选项

  • -Wpadded

    Warn if padding is included in a structure, either to align an element of the structure or to align the whole structure. Sometimes when this happens it is possible to rearrange the fields of the structure to reduce the padding and so make the structure smaller.


如果该结构位于您无法修改的 header 中,那么在 一些 情况下,它可以像这样解决以获得打包副本结构

#include "header.h"

// remove include guard to include the header again
#undef HEADER_H

// Get the packed versions
#define This_Should_Fail This_Should_Fail_Packed
#define This_Should_Succeed  This_Should_Succeed_Packed

// We're including the header again, so it's quite dangerous and
// we need to do everything to prevent duplicated identifiers:
// rename them, or define some macros to remove possible parts

#define someFunc someFunc_deleted
// many parts are wrapped in SOME_CONDITION so this way
// we're preventing them from being redeclared
#define SOME_CONDITION 0

#pragma pack(push, 1)
#include "header.h"
#pragma pack(pop)

#undef This_Should_Fail
#undef This_Should_Succeed

static_assert(sizeof(This_Should_Fail_Packed) == sizeof(This_Should_Fail));
static_assert(sizeof(This_Should_Succeed_Packed) == sizeof(This_Should_Succeed ));

这不适用于使用 #pragma once 的 header 或一些在其他 header 中包含结构的结构