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



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

例如:

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

从 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

虽然,这可能不会完全按照您希望它执行的操作。 例如,如果您的struct包含

  • ABI中的bool,其中字节的任何非零值都是true的,而不仅仅是0x01,或者
  • IEC 559float,因为存在零和 NaN 的多个表示形式,或
  • 具有多个 null 指针表示形式的体系结构上的指针,或
  • 任何带填充的基本类型,或
  • 其他没有唯一对象表示形式的此类类型。

查看链接的 cpp首选项页面了解详细信息,并查看什么类型会使"std::has_unique_object_representations"返回 false?

编辑:检查印第安纳州的答案。

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

是的。

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

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结合使用,以仅针对选定的结构执行此操作。


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

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

用户将不得不依赖显式或基于预处理器的断言,即他们的承诺成立。

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

首先定义所有字段

#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

查看编译器资源管理器上的演示

有关此内容的更多信息,您可以阅读 X 宏的实际使用

另一种非便携式解决方案是将结构的大小与具有#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));

编译器资源管理器演示

请参阅强制C++结构以紧密包装。如果你想拥有一个更便携的包宏,那么试试这个

相关:

  • 如何检查不带填充的结构的大小?
  • 检测结构是否具有填充

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

相关内容

最新更新