我正在寻找一些工会的例子,不是为了了解工会是如何运作的,希望我能做到,而是想看看人们对工会做了哪些黑客行为。
因此,请随时分享您的工会技巧(当然还有一些解释:))
一个经典是表示一个“未知”类型的值,就像在一个简单的虚拟机的核心中一样:
typedef enum { INTEGER, STRING, REAL, POINTER } Type;
typedef struct
{
Type type;
union {
int integer;
char *string;
float real;
void *pointer;
} x;
} Value;
使用它,您可以编写处理“值”而不知道其确切类型的代码,例如实现堆栈等。
由于这是在(旧的,C11 之前的)C 中,因此必须在外部为内部联合指定一个字段名称struct
。在 C++ 中,您可以让union
匿名。选择这个名字可能很难。我倾向于使用单字母的东西,因为它几乎从不孤立地引用,因此从上下文中总是可以清楚地看到发生了什么。
将值设置为整数的代码可能如下所示:
Value value_new_integer(int v)
{
Value v;
v.type = INTEGER;
v.x.integer = v;
return v;
}
struct
在这里,我使用了可以直接返回 s的事实,并且几乎将其视为原始类型的值(您可以分配struct
s)。
这是我每天都用的一个:
struct tagVARIANT {
union {
struct __tagVARIANT {
VARTYPE vt;
WORD wReserved1;
WORD wReserved2;
WORD wReserved3;
union {
LONG lVal; /* VT_I4 */
BYTE bVal; /* VT_UI1 */
SHORT iVal; /* VT_I2 */
FLOAT fltVal; /* VT_R4 */
DOUBLE dblVal; /* VT_R8 */
VARIANT_BOOL boolVal; /* VT_BOOL */
_VARIANT_BOOL bool; /* (obsolete) */
SCODE scode; /* VT_ERROR */
CY cyVal; /* VT_CY */
DATE date; /* VT_DATE */
BSTR bstrVal; /* VT_BSTR */
IUnknown * punkVal; /* VT_UNKNOWN */
IDispatch * pdispVal; /* VT_DISPATCH */
SAFEARRAY * parray; /* VT_ARRAY */
BYTE * pbVal; /* VT_BYREF|VT_UI1 */
SHORT * piVal; /* VT_BYREF|VT_I2 */
LONG * plVal; /* VT_BYREF|VT_I4 */
FLOAT * pfltVal; /* VT_BYREF|VT_R4 */
DOUBLE * pdblVal; /* VT_BYREF|VT_R8 */
VARIANT_BOOL *pboolVal; /* VT_BYREF|VT_BOOL */
SCODE * pscode; /* VT_BYREF|VT_ERROR */
CY * pcyVal; /* VT_BYREF|VT_CY */
DATE * pdate; /* VT_BYREF|VT_DATE */
BSTR * pbstrVal; /* VT_BYREF|VT_BSTR */
IUnknown ** ppunkVal; /* VT_BYREF|VT_UNKNOWN */
IDispatch ** ppdispVal; /* VT_BYREF|VT_DISPATCH */
SAFEARRAY ** pparray; /* VT_BYREF|VT_ARRAY */
VARIANT * pvarVal; /* VT_BYREF|VT_VARIANT */
PVOID byref; /* Generic ByRef */
CHAR cVal; /* VT_I1 */
USHORT uiVal; /* VT_UI2 */
ULONG ulVal; /* VT_UI4 */
INT intVal; /* VT_INT */
UINT uintVal; /* VT_UINT */
DECIMAL * pdecVal; /* VT_BYREF|VT_DECIMAL */
CHAR * pcVal; /* VT_BYREF|VT_I1 */
USHORT * puiVal; /* VT_BYREF|VT_UI2 */
ULONG * pulVal; /* VT_BYREF|VT_UI4 */
INT * pintVal; /* VT_BYREF|VT_INT */
UINT * puintVal; /* VT_BYREF|VT_UINT */
} __VARIANT_NAME_3;
} __VARIANT_NAME_2;
DECIMAL decVal;
} __VARIANT_NAME_1;
};
这是 OLE 自动化变量数据类型的定义。如您所见,它有很多可能的类型。根据您预期的客户端代码的功能,您可以在不同情况下使用的类型有很多规则。并非所有语言都支持所有类型。
它们后面的类型VT_BYREF
由默认通过引用传递参数的语言(例如 VBScript)使用。这意味着,如果您有一些关心变体结构细节的代码(例如 C++)被不关心的代码(例如 VB)调用,那么您必须在需要时仔细取消对变体参数的引用。
byref 类型也用于从函数返回值。还支持使用奇怪的错误命名类型的数组类型SAFEARRAY
- 在 C++ 中很难使用。
如果你有一个字符串数组,你可以将它传递给 vbscript,但它不能被使用(除了打印大小)。要实际读取这些值,数组数据需要是 type VT_BYREF | VT_BSTR
。
联合也常用于语言处理器的词法分析和解析阶段,如编译器和解释器。这是我现在正在编辑的一个。
union {
char c;
int i;
string *s;
double d;
Expression *e;
ExpressionList *el;
fpos_t fp;
}
联合用于将语义值与词法分析器的标记和解析器的产生相关联。这种做法在语法生成器中很常见,比如yacc,它提供了明确的支持。工会可以持有它的任何价值,但当时只能持有其中一个。例如,在输入文件中的任何一点,您要么读取了一个字符常量(存储在 中c
),要么读取了一个整数(存储在 ),要么读取了i
一个浮点数(存储在d
)。语法生成器为根据正在处理的规则确定在任何时候存储哪些值提供了相当大的帮助。
请避免使用 union 进行“黑客攻击”,它们会导致可移植性问题(字节序、对齐问题)。
union 的一个合法用途是在同一个地方存储不同的数据类型,最好使用标签,以便您知道它是哪种类型。请参阅 1800 INFORMATION 的示例。
不要使用联合在数据类型之间进行转换,例如从整数到几个字节。使用 shift 和 masking 代替以实现可移植性。
我们在工作中使用联合来打包消息(C/C++),因此我们可以传递一个带有联合作为数据成员的结构,然后根据结构中的 id 字段访问正确的路径。
在有人将结构写入文件之前工作正常,现在我们仅限于文件中使用的最大数据,因为即使有文件版本,也没有人改变过它......
因此,虽然对内存中的工作很有用,但请避免盲目地将它们写入磁盘或网络。
巧合的是,我在这里的 Stackoverflow 答案中只使用了一个,因此我可以将由 6 位字段组成的单词视为两个 16 位无符号整数。
几年前,我也将一个用于(第一个)ARM C 编译器——当时的指令都是 32 位的,但根据确切的指令有不同的布局。所以我有一个联合来表示一条 ARM 指令,其中包含一组结构,每个结构都有特定指令类型的适当位域。
struct InputEvent
{
enum EventType
{
EventKeyPressed,
EventKeyPressRepeated,
EventKeyReleased,
EventMousePressed,
EventMouseMoved,
EventMouseReleased
} Type;
union
{
unsigned int KeyCode;
struct
{
int x;
int y;
unsigned int ButtonCode;
};
};
};
...
std::vector<InputEvent> InputQueue;
使用联合黑客,我可以简单地制作一个对象向量。我相信这可以变得更干净......但它对我有用 - KISS
#define DWORD unsigned int
#define WORD unsigned short
#define BYTE unsigned char
typedef union _DWORD_PART_ {
DWORD dwWord;
struct {
WORD dwMSB;
WORD dwLSB;
}hw;
struct {
BYTE byMSB;
BYTE byMSBL;
BYTE byLSBH;
BYTE byLSB;
} b;
} DWORD_PART;
这是访问单词部分的简单方法。(一旦完成,平台字节序的任何变化也可以轻松处理)