重载 &= MACRO(向枚举添加功能)会损坏堆栈

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

很久以前,为了提高现有项目的可读性,某些定义被枚举替换,并且枚举通过重载运算符进行了“增强”,以便能够组合枚举。

例如:

enum Props
{
Prop_1 = 0x00000001,
Prop_2 = 0x00000002,
Prop_3 = 0x00000004,

Prop_4 = 0x00000100,

Prop_Group1 = 0x000000FF,
} ;

Props Properties = Prop_1 ; // Initial

Properties &= Prop_2 ; // For some reason we add another property

在我的搜索中,建议我使用以下宏

#define MY_DEFINE_ENUM_FLAG_OPERATORS(ENUMTYPE) \
inline ENUMTYPE operator | (ENUMTYPE a, ENUMTYPE b)     { return ENUMTYPE(((DWORD)a) | ((DWORD)b)); }           \
inline ENUMTYPE &operator |= (ENUMTYPE &a, ENUMTYPE b)  { return (ENUMTYPE &)(((DWORD &)a) |= ((DWORD)b)); }    \
inline ENUMTYPE operator & (ENUMTYPE a, ENUMTYPE b)     { return ENUMTYPE(((DWORD)a) & ((DWORD)b)); }           \
inline ENUMTYPE &operator &= (ENUMTYPE &a, ENUMTYPE b)  { return (ENUMTYPE &)(((DWORD &)a) &= ((DWORD)b)); }    \
inline ENUMTYPE operator ~ (ENUMTYPE a)                 { return ENUMTYPE(~((DWORD)a)); }                       \
inline ENUMTYPE operator ^ (ENUMTYPE a, ENUMTYPE b)     { return ENUMTYPE(((DWORD)a) ^ ((DWORD)b)); }           \
inline ENUMTYPE &operator ^= (ENUMTYPE &a, ENUMTYPE b)  { return (ENUMTYPE &)(((DWORD &)a) ^= ((DWORD)b)); }    \

所以,通过在标头中应用宏:

MY_DEFINE_ENUM_FLAG_OPERATORS(Props)

这实际上在 Borland / CodeGear 和 Embarcadero C++ Builder 的许多版本中一直运行良好,所以我再也没有看过它

今天我正在处理一种可以从此功能中受益的情况,并且枚举有许多可能的状态,超过 32 个,所以我想我将宏更改为 64 位变量而不是 32 位变量(一个宏适合所有情况)

像这样:

typedef unsigned __int64 UI64 ;

#define MY_DEFINE_ENUM_FLAG_OPERATORS(ENUMTYPE) \
inline ENUMTYPE operator | (ENUMTYPE a, ENUMTYPE b)     { return ENUMTYPE(((UI64)a) | ((UI64)b)); }         \
inline ENUMTYPE &operator |= (ENUMTYPE &a, ENUMTYPE b)  { return (ENUMTYPE &)(((UI64 &)a) |= ((UI64)b)); }  \
inline ENUMTYPE operator & (ENUMTYPE a, ENUMTYPE b)     { return ENUMTYPE(((UI64)a) & ((UI64)b)); }         \
inline ENUMTYPE &operator &= (ENUMTYPE &a, ENUMTYPE b)  { return (ENUMTYPE &)(((UI64 &)a) &= ((UI64)b)); }  \
inline ENUMTYPE operator ~ (ENUMTYPE a)                 { return ENUMTYPE(~((UI64)a)); }                    \
inline ENUMTYPE operator ^ (ENUMTYPE a, ENUMTYPE b)     { return ENUMTYPE(((UI64)a) ^ ((UI64)b)); }         \
inline ENUMTYPE &operator ^= (ENUMTYPE &a, ENUMTYPE b)  { return (ENUMTYPE &)(((UI64 &)a) ^= ((UI64)b)); }  \

这是一个错误,因为代码由于变量损坏而开始崩溃,再加上旧枚举的可能值要少得多

在以下情况下注意到(示例)

void Function (void *Input1, Props Properties, bool Whatever)
{
Properties &= Prop_Group1 ; // Corrupts Input1
// ..
}

我注意到Input1被设置为NULL而不是传递给函数的值

我现在意识到宏观从来就不是100%安全的! 特别是

&=
|=
^=

所有枚举(使用 MACRO)在堆栈上必须为 4 个字节,否则可能会出现问题(使用旧的 DWORD 转换 MACRO)。实际上可能就是这样,因为我倾向于写 =

0x00000000
确实是时候回顾一下了

因此,当我更改为 64 位类型 MACRO 时,迄今为止使用的所有枚举都占用了堆栈上的 4 个字节,从而导致了问题。

我觉得宏观可以变得更安全,但我希望你能提供意见,以确保 10 年后我不会再遇到另一个“啊哈”(或“哦操”)时刻

c++ c++builder
1个回答
0
投票

这就是我最终根据我的问题中发布的评论“解决”问题的方式(对此我表示感谢)。

PS。我目前使用两个编译器版本,一个支持 c++ 11,一个支持 c++ 17。

#ifdef
反映了这一点,如果您使用位于中间的编译器,则必须进行调整。就我而言,任何进入“世界”的东西都是使用最新版本构建的,所以我不太担心旧编译器。

// https://docwiki.embarcadero.com/RADStudio/Alexandria/en/Predefined_Macros#C.2B.2B_Compiler_Versions_in_Predefined_Macros
// https://stackoverflow.com/questions/77695203/overloaded-macro-to-add-functionality-to-enum-corrupts-the-stack

#if defined(__BCPLUSPLUS__) && (__BCPLUSPLUS__ >= 0x0760) // >= C++ Builder 11

    // One MACRO fits all

    #define MY_DEFINE_ENUM_FLAG_OPERATORS(ENUMTYPE) \
    inline ENUMTYPE operator | (ENUMTYPE a, ENUMTYPE b)     { return ENUMTYPE(((std::underlying_type_t<ENUMTYPE>)a) | ((std::underlying_type_t<ENUMTYPE>)b)); }         \
    inline ENUMTYPE &operator |= (ENUMTYPE &a, ENUMTYPE b)  { return (ENUMTYPE &)(((std::underlying_type_t<ENUMTYPE> &)a) |= ((std::underlying_type_t<ENUMTYPE>)b)); }  \
    inline ENUMTYPE operator & (ENUMTYPE a, ENUMTYPE b)     { return ENUMTYPE(((std::underlying_type_t<ENUMTYPE>)a) & ((std::underlying_type_t<ENUMTYPE>)b)); }         \
    inline ENUMTYPE &operator &= (ENUMTYPE &a, ENUMTYPE b)  { return (ENUMTYPE &)(((std::underlying_type_t<ENUMTYPE> &)a) &= ((std::underlying_type_t<ENUMTYPE>)b)); }  \
    inline ENUMTYPE operator ~ (ENUMTYPE a)                 { return ENUMTYPE(~((std::underlying_type_t<ENUMTYPE>)a)); }                                                \
    inline ENUMTYPE operator ^ (ENUMTYPE a, ENUMTYPE b)     { return ENUMTYPE(((std::underlying_type_t<ENUMTYPE>)a) ^ ((std::underlying_type_t<ENUMTYPE>)b)); }         \
    inline ENUMTYPE &operator ^= (ENUMTYPE &a, ENUMTYPE b)  { return (ENUMTYPE &)(((std::underlying_type_t<ENUMTYPE> &)a) ^= ((std::underlying_type_t<ENUMTYPE>)b)); }  \

    #define MY_DEFINE_ENUM_FLAG_OPERATORS_DWORD     MY_DEFINE_ENUM_FLAG_OPERATORS
    #define MY_DEFINE_ENUM_FLAG_OPERATORS_UI64      MY_DEFINE_ENUM_FLAG_OPERATORS

#else

    // One MACRO fits all
    // Use tmp value for &=, |= and ^=
    // Enums cannot exceed 8 bytes for this MACRO to work !

    #define MY_DEFINE_ENUM_FLAG_OPERATORS_GEN(ENUMTYPE) \
    inline ENUMTYPE operator | (ENUMTYPE a, ENUMTYPE b)     { return ENUMTYPE(((UI64)a) | ((UI64)b)); }         \
    inline ENUMTYPE &operator |= (ENUMTYPE &a, ENUMTYPE b)  { a = ENUMTYPE((UI64)a | (UI64)b); return a; }      \
    inline ENUMTYPE operator & (ENUMTYPE a, ENUMTYPE b)     { return ENUMTYPE(((UI64)a) & ((UI64)b)); }         \
    inline ENUMTYPE &operator &= (ENUMTYPE &a, ENUMTYPE b)  { a = ENUMTYPE((UI64)a & (UI64)b); return a; }      \
    inline ENUMTYPE operator ~ (ENUMTYPE a)                 { return ENUMTYPE(~((UI64)a)); }                    \
    inline ENUMTYPE operator ^ (ENUMTYPE a, ENUMTYPE b)     { return ENUMTYPE(((UI64)a) ^ ((UI64)b)); }         \
    inline ENUMTYPE &operator ^= (ENUMTYPE &a, ENUMTYPE b)  { a = ENUMTYPE((UI64)a ^ (UI64)b); return a; }      \

    // Enums *must* be 4 bytes for following MACRO to work !
    // Otherwise the MACRO can corrupt the stack
    // Make sure no values are bigger than 0xFFFFFFFF
    // Optionally use syntax: typedef enum: DWORD { } Name ;

    #define MY_DEFINE_ENUM_FLAG_OPERATORS_DWORD(ENUMTYPE) \
    inline ENUMTYPE operator | (ENUMTYPE a, ENUMTYPE b)     { return ENUMTYPE(((DWORD)a) | ((DWORD)b)); }           \
    inline ENUMTYPE &operator |= (ENUMTYPE &a, ENUMTYPE b)  { return (ENUMTYPE &)(((DWORD &)a) |= ((DWORD)b)); }    \
    inline ENUMTYPE operator & (ENUMTYPE a, ENUMTYPE b)     { return ENUMTYPE(((DWORD)a) & ((DWORD)b)); }           \
    inline ENUMTYPE &operator &= (ENUMTYPE &a, ENUMTYPE b)  { return (ENUMTYPE &)(((DWORD &)a) &= ((DWORD)b)); }    \
    inline ENUMTYPE operator ~ (ENUMTYPE a)                 { return ENUMTYPE(~((DWORD)a)); }                       \
    inline ENUMTYPE operator ^ (ENUMTYPE a, ENUMTYPE b)     { return ENUMTYPE(((DWORD)a) ^ ((DWORD)b)); }           \
    inline ENUMTYPE &operator ^= (ENUMTYPE &a, ENUMTYPE b)  { return (ENUMTYPE &)(((DWORD &)a) ^= ((DWORD)b)); }    \

    // Enums *must* be 8 bytes for following MACRO to work !
    // Otherwise the MACRO can corrupt the stack
    // Best make sure there is a value higher than 0xFFFFFFFF
    // Optionally use syntax: typedef enum: UI64 { } Name ;

    #define MY_DEFINE_ENUM_FLAG_OPERATORS_UI64(ENUMTYPE) \
    inline ENUMTYPE operator | (ENUMTYPE a, ENUMTYPE b)     { return ENUMTYPE(((UI64)a) | ((UI64)b)); }         \
    inline ENUMTYPE &operator |= (ENUMTYPE &a, ENUMTYPE b)  { return (ENUMTYPE &)(((UI64 &)a) |= ((UI64)b)); }  \
    inline ENUMTYPE operator & (ENUMTYPE a, ENUMTYPE b)     { return ENUMTYPE(((UI64)a) & ((UI64)b)); }         \
    inline ENUMTYPE &operator &= (ENUMTYPE &a, ENUMTYPE b)  { return (ENUMTYPE &)(((UI64 &)a) &= ((UI64)b)); }  \
    inline ENUMTYPE operator ~ (ENUMTYPE a)                 { return ENUMTYPE(~((UI64)a)); }                    \
    inline ENUMTYPE operator ^ (ENUMTYPE a, ENUMTYPE b)     { return ENUMTYPE(((UI64)a) ^ ((UI64)b)); }         \
    inline ENUMTYPE &operator ^= (ENUMTYPE &a, ENUMTYPE b)  { return (ENUMTYPE &)(((UI64 &)a) ^= ((UI64)b)); }  \

    // Default to the DWORD version

    #define MY_DEFINE_ENUM_FLAG_OPERATORS       MY_DEFINE_ENUM_FLAG_OPERATORS_DWORD

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