我正在寻找一些代码来对 Objective-C 的 @encode()
指令支持的任意 C 类型进行通用相等性比较。我本质上是在寻找类似以下的功能:
BOOL CTypesEqual(void* a, const char* aEnc, void* b, const char* bEnc)
你可以这样调用它:
struct { ... } foo = { yadda, yadda, yadda };
struct { ... } bar = { yadda, yadda, yadda };
BOOL isEqual = CTypesEqual(&foo, @encode(typeof(foo)), &bar, @encode(typeof(bar)));
这是我到目前为止的发现:
启示录 #1
你不能这样做:
BOOL CTypesEqual(void* a, const char* aEnc, void* b, const char * bEnc)
{
if (0 != strcmp(aEnc, bEnc)) // different types
return NO;
NSUInteger size = 0, align = 0;
NSGetSizeAndAlignment(aEnc, &size, &align);
if (0 != memcmp(a, b, size))
return NO;
return YES;
}
...因为对齐限制在成员之间的空间中产生了垃圾。例如,以下将无法通过基于 memcmp
的相等性检查,尽管这两个结构对我来说是相等的:
typedef struct {
char first;
NSUInteger second;
} FooType;
FooType a, b;
memset(&a, 0x55555555, sizeof(FooType));
memset(&b, 0xAAAAAAAA, sizeof(FooType));
a.first = 'a';
a.second = ~0;
b.first = 'a';
b.second = ~0;
启示录 #2
你可以滥用 NSCoder
来做到这一点,像这样:
BOOL CTypesEqual(void* a, const char* aEnc, void* b, const char * bEnc)
{
if (0 != strcmp(aEnc, bEnc)) // different types
return NO;
NSMutableData* aData = [[NSMutableData alloc] init];
NSArchiver* aArchiver = [[NSArchiver alloc] initForWritingWithMutableData: aData];
[aArchiver encodeValueOfObjCType: aEnc at: a];
NSMutableData* bData = [[NSMutableData alloc] init];
NSArchiver* bArchiver = [[NSArchiver alloc] initForWritingWithMutableData: bData];
[bArchiver encodeValueOfObjCType: bEnc at: b];
return [aData isEqual: bData];
}
一切都很好,并提供了预期的结果,但结果谁知道有多少堆分配(至少 6 个)并使操作应该相对便宜,非常昂贵。
启示录#3
你不能为此使用 NSValue
。如下所示,以下内容不起作用:
typedef struct {
char first;
NSUInteger second;
} FooType;
FooType a, b;
memset(&a, 0x55555555, sizeof(FooType));
memset(&b, 0xAAAAAAAA, sizeof(FooType));
a.first = 'a';
a.second = 0xFFFFFFFFFFFFFFFF;
b.first = 'a';
b.second = 0xFFFFFFFFFFFFFFFF;
NSValue* aVal = [NSValue valueWithBytes: &a objCType: @encode(typeof(a))];
NSValue* bVal = [NSValue valueWithBytes: &b objCType: @encode(typeof(b))];
BOOL isEqual = [aVal isEqual: bVal];
启示录 #4
Cocotron's NSCoder
implementation对所有困难的东西(任意 struct
、union
等)下注,因此没有进一步灵感的来源。
到目前为止我的尝试
所以我开始研究这个,手头有文档,我大致做到了这一点:
BOOL CTypesEqual(void* a, const char* aEnc, void* b, const char * bEnc)
{
if (0 != strcmp(aEnc, bEnc)) // different types
return NO;
return SameEncCTypesEqual(a, b, aEnc);
}
static BOOL SameEncCTypesEqual(void* a, void* b, const char* enc)
{
switch (enc[0])
{
case 'v':
{
// Not sure this can happen, but...
return YES;
}
case 'B':
case 'c':
case 'C':
case 's':
case 'S':
case 'i':
case 'I':
case 'l':
case 'L':
case 'q':
case 'Q':
case 'f':
case 'd':
case '@':
case '#':
{
NSUInteger size = 0, align = 0;
NSGetSizeAndAlignment(enc, &size, &align);
const int result = memcmp(a, b, size);
if (result)
return NO;
break;
}
case ':':
{
if (!sel_isEqual(*(SEL*)a, *(SEL*)b))
return NO;
}
case '*':
{
if (strcmp((const char *)a, (const char *)b))
return NO;
}
case '{':
{
// Get past the name
for (const char *prev = enc - 1, *orig = enc; prev < orig || (prev[0] != '=' && prev[0] != '\0' && enc[0] != '}'); prev++, enc++);
// Chew through it
for (NSUInteger pos = 0, size = 0, align = 0; enc[0] != '}' && enc[0] != '\0'; enc++, pos += size, size = 0, align = 0)
{
NSGetSizeAndAlignment(enc, &size, &align);
// figure out where we should be w/r/t alignment
pos = align * (pos + align - 1) / align;
// Descend
BOOL sub = SameEncCTypesEqual(((uint8_t*)a) + pos, ((uint8_t*)b) + pos, enc);
if (!sub)
return NO;
}
break;
}
case '[':
{
// Skip the '['
enc++;
// Get numElements
int numElements = 0;
sscanf(enc, "%d", &numElements);
// Advance past the number
for (; enc[0] <= '9' && enc[0] >= '0'; enc++);
// Get the size
NSUInteger size = 0, align = 0;
const char * const elementType = enc;
NSGetSizeAndAlignment(elementType, &size, &align);
for (NSUInteger i = 0; i < numElements; i++)
{
BOOL elementEqual = SameEncCTypesEqual(((uint8_t*)a) + i * size, ((uint8_t*)b) + i * size, elementType);
if (!elementEqual)
return NO;
}
break;
}
case '(':
{
NSLog(@"unions?! seriously, bro?");
return NO;
break;
}
default:
{
NSLog(@"Unknown type: %s", enc);
return NO;
break;
}
}
return YES;
}
...当我开始使用 union 时,我对自己说,“Self,你为什么要这样做?这正是那种有上百万个小角落案例要遗漏的代码,而且真的,它看起来像一些应该已经被其他更有耐心的人写过很多次的东西。”所以我在这里。任何人都知道在(公共(public))框架或“野外”中经过实践检验的这个实现没有使用 NSCoder
的所有额外重量吗?
最佳答案
为什么不直接创建一个跳过填充的函数呢?
首先,您需要猜测填充策略。这是简单的部分。
然后你使用@encode
提供的编码信息将数据类型映射到掩码,并使用这些掩码进行比较。
一个简单的例子:
struct m { char a; int b; };
struct n { char c; struct m d; int e; };
可以转换为(假设 sizeof(int) 为 4):
struct_m_mask = { 1, 4, 0 };
struct_n_mask = { 1, 1, 4, 4, 0 };
在对齐允许的情况下,表示的优化当然是可能的,例如:
struct_b_mask = { 2, 4, 4, 0 };
然后,您可以遍历这个数组来进行比较。 A[n+1] - A[n]
给出孔的大小,如果前面没有孔,比如 (b, e)
,那么你可以合并它们。
这是我能想到的最简单的方法。也许您可以实现更复杂的技巧。
顺便说一句,我的猜测是假设这些东西是常量,编译器可以在编译时计算掩码,但也许这要求太高了......
关于objective-c - 使用 Objective-C @encode 检查任意 C 类型的相等性——已经存在了吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20169425/