2009-11-08 3 views
0

Возможно ли иметь массив элементов (структур), где значения различаются по размеру?C: Массив с элементами различного размера

Проблема, с которой я сталкиваюсь, заключается в том, что я не знаю, как получить доступ к элементу, так как он требует приведения. «Массив» просто содержит указатели на структуры. Поэтому я решил пойти с пустотой **. Поскольку не каждый из элементов имеет один и тот же тип, этот тип также должен быть сохранен в массиве, чтобы я знал, что делать, не делая грубых предположений. Однако это не очень эффективно. Нет ли более эффективного способа?

ответ

6

Если вам не нравится слепки, вы всегда можете использоваться союзы (и флаг, чтобы указать, какой тип объединение должно быть истолковано как)

#include <stdio.h> 

typedef struct { 
    int a; 
} A; 

typedef struct B { 
    double b; 
} B; 

typedef struct C { 
    char c[20]; 
} C; 

typedef enum { 
    TypeA, 
    TypeB, 
    TypeC, 
} type; 

typedef struct { 
    type type; 
    union { A*a; B*b; C*c; } p; 
} TypedPointer ; 

void foreach (TypedPointer* list, void (*fn)(TypedPointer)) 
{ 
    while (list->p.a) { 
     fn(*list); 
     ++list; 
    } 
} 

void print_member (TypedPointer ptr) 
{ 
    switch (ptr.type) { 
     case TypeA: printf("A (%d)\n", ptr.p.a->a); break; 
     case TypeB: printf("B (%f)\n", ptr.p.b->b); break; 
     case TypeC: printf("C (%s)\n", ptr.p.c->c); break; 
    } 
} 

int main() 
{ 
    A a = { .a = 42 }; 
    B b = { .b = 1.01 }; 
    C c = { .c = "Hello World!" }; 

    TypedPointer ptrs[] = { 
     { .type = TypeA, .p.a = &a }, 
     { .type = TypeB, .p.b = &b }, 
     { .type = TypeC, .p.c = &c }, 
     { .type = 0, .p.a = 0} }; 

    foreach(ptrs, print_member); 

    return 0; 
} 
+0

Спасибо. Думаю, я пойду с вашим подходом. Это самое чистое и не связано с линией, подверженной ошибкам. Один последний вопрос: есть ли причина, по которой вы используете указатели внутри профсоюза? Если я прав, удаление их позволит сэкономить четыре байта (по крайней мере на x86) для каждого элемента union. Единственная причина, по которой я могу думать, это ваш цикл while, поскольку для повторения массива потребуется дополнительная встречная переменная. – user206268

+0

Потому что в вашем OP вы сказали, что у вас есть массив указателей на разные структуры. Вы можете сделать это, используя объединение структур напрямую, хотя это может также потерять пространство, поскольку объединение будет размером с наибольший член. –

0

Для этого мы используем массив указателей на фактические объекты.

Указатели имеют одинаковый размер.

Объекты, на которые они указывают, могут быть разных размеров.

«Дискриминационный союз» хорошо работает для этого.

typedef struct { ... } this_type; 
typedef struct { ... } that_type; 
typedef struct { 
    int subtype; 
    union { 
     this_type this; 
     that_type that 
    } 
} discriminated_union; 

An указатели на массив discriminated_union работает хорошо.

+1

Если я правильно понял, OP, типы объектов отличаются. –

+0

Спасибо за мгновенный ответ. Да, это именно то, о чем я думал. Проблема в том, что я не знаю, как отдать полученный элемент, поскольку оригинальный тип неизвестен. – user206268

+0

Стандартный подход - использовать «дискриминационный союз» различных типов. –

0

Броски не являются неэффективными, так как они просто говорят компьютеру, как интерпретировать область памяти. Когда они скомпилированы для сборки, компилятор создаст сборку, которая по мере необходимости будет интерпретировать область памяти.

+0

Это не относится к языкам, таким как C# и Java, и это может привести к тому, что программисты, используемые на этих языках, избегают приведения в C по соображениям производительности. – Jonatan

10

Нет, в C каждый элемент массива должен быть одного типа (и, следовательно, такого же размера).

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

enum ElemType { 
    TypeNull, 
    TypeFoo, 
    TypeBar 
]; 

struct Elem { 
enum ElemType type; 
void *realElem; 
}; 

Вы должны были бы обновить «тип» с фактическим типом вставки, и принимать решения о том, что, когда вы читаете массив, и вы храните указатель на фактический элемент в элементе «realElem».

struct Elem arr[42]; 
... 
switch(arr[k].type) { 
    case TypeFoo: 
    handleFoo(arr[k].realElem); 
    break; 
    ... 
} 
+0

Спасибо. Я надеялся обойти сохранение типа каким-то образом, но с ElemType-enum это, похоже, не слишком много накладных расходов. – user206268

+1

Но как только вы будете использовать объект, вам нужно будет знать, какого типа у них нет? Таким образом, вы должны хранить эту информацию, используя либо компилятор (который не поддерживается в C), либо метаданные, как указано выше. Однако вы могли бы сохранить обратные вызовы в структуре для перехвата данных, для более OO-подхода. – Jonatan

2

Нет, C массивы имеют постоянный размер, который должен быть известен в compiletime. Но вы можете обойти это, используя указатели и динамическое распределение.

Проблема здесь не эффективность настолько, насколько это безопасно. Кастинг не имеет стоимости исполнения во время выполнения, это всего лишь механизм для общения с компилятором, попросите его «доверять вам» и предположим, что за последовательностью бит есть определенный тип. Мы говорим о безопасности, потому что, если вы ошибаетесь, все может произойти во время выполнения.

В C (статическом языке) невозможно построить массив разных типов. То, что вы можете сделать, это построить массив определенной структуры, но удержать указатели на объекты с различными размерами. Например, если у struct есть указатель char*, в этом случае каждый объект содержит строку одной или другой длины и, следовательно, имеет переменный размер (в терминах общей памяти, используемой во время выполнения, а не в виде непрерывного пространства).

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

+0

Это интересная идея. Правильно ли я понял, что массив состоит только из указателей функций? Но каковы функции на самом деле? Возврат значения и связанного с ним типа? Hm, это приведет к многочисленным вызовам функций, учитывая размеры массива в 10 000 единиц. Однако предлагаемый подход, как представляется, более эффективен. – user206268

Смежные вопросы