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

Posted

技术标签:

【中文标题】编译时检查以确保结构中的任何地方都没有填充【英文标题】:Compile-time check to make sure that there is no padding anywhere in a struct 【发布时间】:2020-01-09 19:04:59 【问题描述】:

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

例如:

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;
;

【问题讨论】:

仅适用于将每个成员变量都传递给它的宏/模板。但是,通过确保所有结构都没有填充,您真正想做什么? @selbie 我有一些在 void* 内存上工作的函数(用于散列、比较、.. 之类的东西),并且在某些情况下我想避免编写特殊散列/比较的样板类型的函数;我只想散列并比较字节。但是如果结构中有填充,那么这样做是不安全的,因为这些字节的值是未定义的。 如果你在初始化它的成员之前可靠地将结构的字节清零,那么你所有的散列和比较不都是一致的吗? (免责声明,有人会因为提出该建议而责备我未定义的行为。) 今天没有。也许在 C++23 中。在探索 C++ 的编译时反射方面投入了大量资金。 @selbie 具有相当大的性能成本(并且还破坏了与不支持未对齐加载和存储的处理器的兼容性),但它也不能解决我的问题,因为我不知道前面我正在检查什么类型,类型是模板输入。 【参考方案1】:

编辑:检查Kerndog73's answer。

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

是的。

您可以将所有成员的 sizeof 相加,并将其与类本身的大小进行比较:

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 中。目前,有一些基于将类定义包装在宏中的解决方案。

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


类型我正在检查,类型是模板输入。

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

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

【讨论】:

这还不够。如果任何成员本身就是结构,我还必须枚举他们的成员,等等......我有自己的预处理器,所以我可以通过生成该代码来进行这样的检查,我只是想知道是否有任何方法可以做到这一点香草 C++。谢谢,这似乎是目前最好的事情! @Kalinovcic 是的,如果成员内的填充很重要,那么必须对每个成员递归地进行此检查。使用标准预处理器来做这件事超出了我的技能,但使用自定义预处理器可能是完全可行的。 @Justin 第二件事对我不起作用,因为它太侵入性了。第一个建议很有趣,我想它会起作用,有趣的是它是可能的,但是当包含所有这些标头时,我害怕额外的编译时间。 :) 对于非常简单的检查来说,代码太多了,在这种情况下,我宁愿使用自定义预处理器。 @Kalinovcic 有一些技术可以在没有预处理器的情况下模拟反射。一个是适用于结构的magic-get 库;它可以遍历结构的成员。另一种技术是让任何可反射类型具有auto reflect() const return std::tie(a, b, c); static auto members() return std::tuple(&TheType::a, &TheType::b, &TheType::c); @Justin 我这样做是为了避免需要哈希和比较类型函数的样板,因此要求成员列表函数似乎不是一个好的解决方案。随着结构的变化,它也很可能变得过时。【参考方案2】:

从 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 页面了解详细信息。

【讨论】:

虽然请注意,对于所有包含浮点数的结构(在符合 IEEE-754 的系统上),无论它们是否包含填充,这都会失败。 另请注意,它不适用于非 TriviallyCopyable 结构 哇,这和我想要的非常接近。不过,我也需要它来处理带有浮点数的结构,但我不知道它存在。谢谢!【参考方案3】:

要在不重新键入每个结构成员的情况下获得总字段大小,您可以使用 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

【讨论】:

我知道 X 宏,但我想避免像这样的宏地狱解决方案。我有自己的自定义预处理器,所以我可以使用它来制作更好的东西。我一直在寻找一个非侵入性的解决方案,但我想我解释得不够好。谢谢!【参考方案4】:

另一种不可移植的解决方案是将结构的大小与带有#pragma pack__attribute__((packed))打包 版本进行比较。 #pragma pack 也受到许多其他编译器的支持,例如 GCC 或 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。如果你想要一个更便携的包宏,那么try this

相关:

How to check the size of struct w/o padding? Detect if struct has padding

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

-Wpadded

如果结构中包含填充,则发出警告,以对齐结构的元素或对齐整个结构。有时发生这种情况时,可以重新排列结构的字段以减少填充,从而使结构更小。


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

#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 的标头或某些在其他标头中包含结构的结构

【讨论】:

如果有一种方法可以在声明站点以外的地方打包一个结构体(比如生成一个结构体的“打包副本”),这将很有效。不过我不知道这样的事情。 是的,很遗憾,您必须在申报地点这样做。但是,对于一些简单的标题,您可以解决这个问题。查看我的编辑 我想避免打包实际的结构,因为这会对性能产生相当大的影响,并且会破坏与不支持未对齐加载和存储的 cpu 架构的兼容性。我不想强制类型与我的函数兼容,我只想禁止不兼容的类型。 啊,对不起,我看错了你的代码,我看到你包含了两次标题。是的,这会奏效。虽然它有很多设置和样板,但它错过了避免每种类型的样板的初衷。

以上是关于编译时检查以确保结构中的任何地方都没有填充的主要内容,如果未能解决你的问题,请参考以下文章

我们如何强制编译器没有结构填充?请解释

在 c/c++ 中包括结构和编译

C ++编译时未实现的检查

TypeScript:如何在编译时声明固定大小的数组以进行类型检查

是否有任何“现代”框架支持在编译时检查数据绑定?

查找预编译头文件时跳过