Как получить доступ к элементу структуры в функции, которая получает его как тип void *? - PullRequest
3 голосов
/ 10 апреля 2010

Я хочу иметь функцию, которая принимает различные типы структур в качестве аргумента. Итак, поскольку у меня нет определенного типа, я должен использовать void *. Теперь вопрос: когда я передаю структуру этой функции, как я могу получить доступ к известному члену этой структуры внутри функции? В частности, я знаю, что все структуры имеют член str1, и я хочу, например, напечатать его. Вот пример кода:

struct {  
  char* str1;  
  float tt1; } var1 = {"This is me", 12};  

struct {  
   char* str1;  
   int   tt2; } var2 = {"This is you", 18};  

void printStruct(void* str)  
{  
   printf("\n the structure string is %s", ??);   
  //can I put something in ?? to print the string?  
}

main(....)  
{  
   printStruct(&var1);  
   printStruct(&var2);  
}

Ответы [ 6 ]

7 голосов
/ 10 апреля 2010

Вы можете передать указатель на функцию, чтобы отделить функцию от ее пользователей

void printStruct(void* data, char * (*namef)(void *data)) {  
   printf("\n the structure string is %s", namef(data));    
}

Тогда предположим, что вы передаете struct S1* функции, вы можете передать ей геттер, например:

char * whois(void *data) {
  return ((struct S1*)data)->str1;
}

int main(void) {
  struct S1 s = {"This is me", 12};
  printStruct(&s, whois);
}

Вы можете использовать более уродливую альтернативу с offsetof хотя

void printStruct(void* data, size_t named) {  
   printf("\n the structure string is %s", *(char**) ((char*)data + named));    
}

int main(void) {
  struct S1 s = {"This is me", 12};
  printStruct(&s, offsetof(struct S1, str1));
}

offsetof - это макрос, который дает вам смещение члена в структуре. В функции вы позиционируете указатель на член char* (поэтому вы должны привести к char**, чтобы получить соответствующий тип) и получить значение члена.

Однако я бы предпочел первый способ использования функций. Таким образом, функция печати даже не должна знать, что data указывает на структуру. Он может передать его как черный ящик функции получения, и в нем вы можете позволить компилятору обрабатывать вычисления низкоуровневых смещений за вас.

6 голосов
/ 10 апреля 2010

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

struct X
{
char* str1;  
float tt1; } var1 = {"This is me", 12};  

void printStruct(void* str)
{
    ((struct X *)str)->str1;
    ...
}

Обратите внимание на использование скобок; в результате происходит приведение типа void * к структуре var2 *, и этот тип внутри внешних () скобок может быть доступен как переменная этого типа.

3 голосов
/ 10 апреля 2010

Вы должны дать имена своим структурам. Например,

struct foo {
   char * str1;
   float tt1;
};

Тогда вы можете объявить свои экземпляры так:

struct foo var1 = { "This is me", 12 };

Затем вы можете привести void * к struct foo * в вашей функции:

void printStruct(void * ptr) {
   printf("str=%s\n", ((struct foo *)ptr)->str1);
}

И вы можете назвать это так:

int main(...) {
   struct foo var1 = { "This is me", 12 };
   printStruct(&var1);
}

Обновление: Комментатор Йоханнес был прав, мой ответ не учитывает тот факт, что оригинальный автор сказал, что он может не знать тип каждой структуры. Обновленное решение ниже:

Поскольку вы знаете, что все ваши структуры содержат char * str1, вы можете воспользоваться «моделью плоской памяти» C, приведя ваши структуры непосредственно к общей теговой структуре, которая содержит только char * str1. Это будет работать только если char * str1 является первым элементом структур .

struct base { char * str1; };
struct { char * str1; float tt1; } var1 = { "This is me", 12 };
struct { char * str1, int   tt2; } var2 = { "This is me", 18 };

void printStruct(void * ptr) {
   printf("str is %s\n", ((struct base *)ptr)->str1);
}

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

Другой вариант - определить struct, который использует объединение с перечислителем для отслеживания того, какой тип фактически используется в объединении:

enum { FLOAT_TYPE, INT_TYPE };
struct foo {
   char * str1;
   union {
      float tt1;
      int tt2;
   } numericValue;
   int unionType;
};

Это позволит вам определить переменные следующим образом:

struct foo var1 = { .str1 = "This is me", .numericValue.tt1 =12, .unionType = FLOAT_TYPE };
struct foo var2 = { .str1 = "This is me", .numericValue.tt2 =18, .unionType = INT_TYPE };

Тогда вам не нужно обрабатывать различные типы структур, вместо этого вы можете привести свой пустой указатель к struct foo *:

void printStruct(void * ptr) {
   struct foo * p = (struct foo *)ptr;
   printf("string is %s\n", p->str1);
}

Это может потребовать немного больше работы, но это намного чище, ИМО.

2 голосов
/ 10 апреля 2010

Во-первых, простой пример:

int booger(int type, void * st) {
   switch(type) {
      case foo:
        struct foo * f = (struct foo *)st;
        /// do stuff
        break;
      case bar:
      /// ...

Теперь для вашей структуры: Определите новую структуру, которая содержит только первый член - строку: struct st { char * str; }; Теперь вы можете передать все свои другие структуры в вашу функцию, приведя их к virtual_st *, а затем сделать то же самое, что и в первом примере, чтобы получить действительные члены.

int baz(struct st * s) {
    if (!strcmp(s->str, "foo") ) {
       // ...

EDIT: Кстати, это похоже на то, как это делают компиляторы c ++, которые выводят c, за исключением того, что начальное поле является указателем на структуру типов, которая содержит указатели на функции-члены и статические члены класса. Я не знаю точно, как работает множественное наследование.

2 голосов
/ 10 апреля 2010

Для большинства компиляторов это просто безопасно привести и получить доступ к члену таким образом, так как он является первым членом обеих ваших структур, вероятность того, что выравнивание будет одинаковым.

Дело в том, что, поскольку вам нужно будет привести указатели в структуру, они должны иметь теги:

struct S1 { char* str1; float tt1; } var1 = {"This is me", 12};

struct S2 { char* str1; int tt2; } var2 = {"This is you", 18};

void printStruct(void* str) { printf("\n the structure string is %s", ((struct S1 *)str)->str1); }

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

0 голосов
/ 10 апреля 2010

Если вы передаете void*, я не понимаю, как метод мог узнать что-либо о том, что вы передаете, кроме того, что это адрес чего-то. Это означает, что вы должны разыграть его, прежде чем сможете его использовать, или же убедитесь, что все struct, которые вы передаете, имеют char* в одном и том же месте каждый раз.

...