Подтвердить что ты не робот

Проверка равенства произвольных типов C с использованием Objective-C @encode - уже существует?

Я ищу некоторый код для сравнения общего равенства произвольных типов 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?

4b9b3361

Ответ 1

Почему бы просто не сделать функцию, которая пропускает дополнение?

Во-первых, вам нужно угадать политику заполнения. Это легкая часть.

Затем вы используете информацию кодирования, предоставленную @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), тогда вы можете объединить их.

Это самый простой способ, с помощью которого я мог бы придумать. Возможно, вы можете реализовать более сложные трюки.

Кстати, я предполагаю, что если материал постоянный, компилятор может вычислить маски во время компиляции, но, возможно, это слишком много спрашивает...

Ответ 2

Что касается проблемы заполнения, вам может потребоваться сделать что-то вроде функции для каждой структуры, которая превращает ее в массив для сравнения или возвращает одно значение в индексе. Это не очень оптимально, но, как правило, почему алгоритмы сортировки позволяют передавать указатели на функции, которые выполняют фактическое сравнение.