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

问题描述 投票:0回答:4

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

例如:

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++ struct padding static-assert
4个回答
7
投票

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


6
投票

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

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

是的。

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

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
结合使用,只对选定的结构进行。


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

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

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


1
投票

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

在编译器资源管理器上查看demo

有关此的更多信息,您可以阅读X-Macros 的实际使用


1
投票

另一种不可移植的解决方案是将结构的大小与带有 #pragma pack

__attribute__((packed))
packed
版本进行比较。
#pragma pack
也得到许多其他编译器的支持,例如 GCCIBM 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));

Compiler Explorer 上的演示

参见强制 C++ 结构紧密打包。如果你想要一个更便携的包宏那么试试这个

相关:


GCCClang中有一个

-Wpadded
选项用于此目的

  • -Wpadded

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


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

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

© www.soinside.com 2019 - 2024. All rights reserved.