Я ищу некоторый код для сравнения общего равенства произвольных типов C, поддерживаемых директивой Objective-C @encode()
. Я в основном ищу функцию, например:
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 NSCoder
отражает все тяжелые вещи (произвольные struct
s, union
s и т.д.), таким образом, не является источником дальнейшего вдохновение.
Моя попытка пока
Итак, я начал с этого, документы в руке, и я грубо получил это далеко:
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;
}
... и о том, когда я добрался до профсоюзов, я сказал себе: "Я, почему ты это делаешь? Это точно такой код с миллионным короткими случаями, чтобы пропустить, и действительно, похоже то, что должно было быть написано уже несколько раз, другими людьми с большим терпением". И вот я здесь. Кто-нибудь знает о проверенной и реалистичной реализации этого в (общедоступных) рамках или "в дикой природе", которые не имеют лишнего веса при использовании NSCoder
?