检查 GUID 是否为空(在 C 语言中)

4
我想检查一个GUID结构是否为空/所有字段都为0。以下是我写的代码:
#include <windows.h>

static BOOL IsEmptyGuid(const GUID * const pGuid)
{
    return \
    (pGuid->Data1 == 0) &&
    (pGuid->Data2 == 0) &&
    (pGuid->Data3 == 0) &&
#ifdef _WIN64
    (*(DWORD64 *)pGuid->Data4 == 0);
#else
    (*(DWORD *)pGuid->Data4 == 0) && (*(DWORD *)(pGuid->Data4 + 4) == 0);
#endif
}

/* GUID definition from MSDN
typedef struct _GUID {
    DWORD Data1;
    WORD  Data2;
    WORD  Data3;
    BYTE  Data4[8];
} GUID;
*/


int main() {
    GUID guid1;
    guid1.Data1 = 0;
    guid1.Data2 = 0;
    guid1.Data3 = 0;
    memset(guid1.Data4, 0x0, 8);

    printf("Result: %u\n", IsEmptyGuid(&guid1));
}

一种更安全的检查字段 Data4 是否等于 0 的方法是迭代每个字节并检查其值。但是,我认为上面的代码更具表现力。

我想知道,这样做是否正确?是否安全?

谢谢!


1
@RbMm请不要在评论区提供无法被踩的错误答案。 - user694733
@user694733 - 你为什么认为这是不正确或不安全的?绝对正确。而且我认为这个答案太简短了。 - RbMm
顺便说一句:return \ 中的反斜杠没有任何作用。 - underscore_d
3
有没有什么特别的原因不使用 GUID guid_empty = { 0, 0, 0, { 0, 0, 0, 0, 0, 0, 0, 0 } }; 来创建空 GUID,然后再使用内置函数 IsEqualGUID 呢?请进行翻译。 - vasek
2
不,我只是不知道它的存在。我在寻找一个告诉我它是否为空的函数。这是一种视角的改变,去看它是否相等。很好,谢谢! - thrillseeker
4
不要重新发明轮子,使用IsEqualGUID()函数来将GUID值与GUID_NULL进行比较。 - Hans Passant
2个回答

3

代码不正确。它违反了严格别名规则(N1570 §6.5 p7),导致未定义行为。

一个对象的存储值只能通过以下类型之一的lvalue表达式访问:88)

  • 与对象的有效类型兼容的类型
  • 与对象的有效类型兼容的类型的限定版本
  • 对应于对象的有效类型的带符号或无符号类型
  • 对应于对象有效类型的带符号或无符号类型的限定版本
  • 在其成员中包括上述类型之一的聚合或联合类型(包括递归地对子聚合或包含联合的成员进行操作)
  • 字符类型。

88) 这个列表的目的是指定对象可能或可能不被别名的情况。

确切地说,当你使用不匹配的类型解除引用指针时,将发生未定义行为:

DWORD64 * temp = (DWORD64 *)pGuid->Data4; // Allowed, but implementation defined
DWORD64 temp2 = *temp;                    // Undefined behaviour

使用循环逐个检查每个元素,或将其与相同大小的填充为零的数组比较memcmp
如评论中所述,有些编译器允许禁用严格别名,但应避免这样做,因为它使代码不够可移植,并且仍然存在潜在的对齐问题。

1
@RbMm 这确实是未定义行为。它被定义为通过[[un]signed] char*指针重新解释其他类型,但反过来则不行。(假设BYTE肯定是char类型) - underscore_d
1
只要一直说下去,它就会神奇地变成真的。或者引用一个支持你观点的来源。 - underscore_d
1
这不重要。这个答案是正确的,通过不同类型的指针访问char数组是未定义的行为 - user2371524
1
它可能在微软的编译器上运行良好,也许他们不太可能改变这一点。但这并不意味着它是“正确”的或者是好的代码。有简单的方法来获得完全定义的行为,特别是因为它是C(而不是C ++),所以没有理由继续使用脆弱的代码。 - underscore_d
1
@RbMm 你一直在毫无依据地说这句话。事实已经多次证明,编译器可以利用别名规则来优化掉像你这样的无效代码。这与你模糊的“二进制兼容性”概念毫不相关,其他人都能够在不编写根本错误代码的情况下实现这一点。 - underscore_d
显示剩余8条评论

-6
当未定义 _WIN64
(*(DWORD *)pGuid->Data4 == 0) && (*(DWORD *)(pGuid->Data4 + 4) == 0);

case - 这是绝对安全和正确的 - Data4 成员对齐到 DWORD (4) 并且大小为 2 个 DWORD (2 * sizeof(DWORD) == 8)

对于 _WIN64Data4 的对齐仍然是 DWORD (4),因为所有结构体的对齐都是 4 (C_ASSERT(__alignof(GUID)==4)) - 当代码 (*(DWORD64 *)pGuid->Data4 == 0); 假设 8 字节数据对齐参考。例如 x64 通常处理器在引用未对齐的数据时不会生成异常。但是在另一个平台上可能会出现这种情况。无论如何,访问未对齐的数据对性能都不利。所以必须进行检查:

BOOL IsEmptyGuid(const GUID * pGuid)
{
    return (pGuid->Data1 == 0) &&
        (pGuid->Data2 == 0) &&
        (pGuid->Data3 == 0) &&
        (*(DWORD *)pGuid->Data4 == 0) && (*(DWORD *)(pGuid->Data4 + 4) == 0);
}

适用于所有平台。

关于成员偏移、对齐等问题,这是众所周知的,在这里不可能存在任何未定义行为。否则,在两个代码之间将无法进行任何二进制接口。


有趣,这里有什么不正确:

  1. Data4 不是指向8个连续字节的指针吗?
  2. Data4 距离GUID没有8个字节的偏移吗?
  3. Data4 没有按4字节对齐(当然,如果pGuid本身适当对齐)?
  4. 我们不能将8字节内存(对齐于DWORD)作为2个DWORD来检查吗?

以及一些来自Windows头文件的代码:

// Faster (but makes code fatter) inline version...use sparingly
#ifdef __cplusplus
__inline int InlineIsEqualGUID(REFGUID rguid1, REFGUID rguid2)
{
   return (
      ((unsigned long *) &rguid1)[0] == ((unsigned long *) &rguid2)[0] &&
      ((unsigned long *) &rguid1)[1] == ((unsigned long *) &rguid2)[1] &&
      ((unsigned long *) &rguid1)[2] == ((unsigned long *) &rguid2)[2] &&
      ((unsigned long *) &rguid1)[3] == ((unsigned long *) &rguid2)[3]);
}

__inline int IsEqualGUID(REFGUID rguid1, REFGUID rguid2)
{
    return !memcmp(&rguid1, &rguid2, sizeof(GUID));
}

#else   // ! __cplusplus

#define InlineIsEqualGUID(rguid1, rguid2)  \
        (((unsigned long *) rguid1)[0] == ((unsigned long *) rguid2)[0] &&   \
        ((unsigned long *) rguid1)[1] == ((unsigned long *) rguid2)[1] &&    \
        ((unsigned long *) rguid1)[2] == ((unsigned long *) rguid2)[2] &&    \
        ((unsigned long *) rguid1)[3] == ((unsigned long *) rguid2)[3])

#define IsEqualGUID(rguid1, rguid2) (!memcmp(rguid1, rguid2, sizeof(GUID)))

#endif  // __cplusplus

#ifndef __IID_ALIGNED__
    #define __IID_ALIGNED__
    #ifdef __cplusplus
        inline int IsEqualGUIDAligned(REFGUID guid1, REFGUID guid2)
        {
            return ((*(PLONGLONG)(&guid1) == *(PLONGLONG)(&guid2)) && (*((PLONGLONG)(&guid1) + 1) == *((PLONGLONG)(&guid2) + 1)));
        }
    #else // !__cplusplus
        #define IsEqualGUIDAligned(guid1, guid2) \
            ((*(PLONGLONG)(guid1) == *(PLONGLONG)(guid2)) && (*((PLONGLONG)(guid1) + 1) == *((PLONGLONG)(guid2) + 1)))
    #endif // !__cplusplus
#endif // !__IID_ALIGNED__

3
“这里不能有任何未定义行为。” 然而,实际上却存在。通过引用类型转换后的指针来访问其指向的对象违反了内存别名规则,这是被标准明确禁止的。既然存在易于理解且符合规范的方式来完成同样的操作,为什么还要鼓励人们写容易出错的代码呢? - underscore_d
2
@ServéLaurijssen,我认为劝阻编写违反语言定义且不需要在任何编译器上工作的代码,并且存在易于编写的明确定义的替代方法,这并不过分挑剔。 - underscore_d
3
由于你不了解编译器如何利用严格别名规则,所以你写了很多无意义的话:编译器可以根据代码正确的假设对代码进行优化。(任何类型的二进制表示与此无关) - user2371524
1
@RbMm 我不怀疑这一点,问题是你不知道C语言(在抽象机器的层面上)是如何工作的。 - user2371524
2
我放弃了。你拒绝与实际问题接触,认为不断重复其他内容就能淹没它。根据标准,该代码是未定义行为。我不在乎它是否在你的编译器上运行正常。这并非必须的,而且这是错误的建议。结束了。 - underscore_d
显示剩余26条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接