Есть ли способ определить «общую» структуру для нескольких номеров и типов параметров - PullRequest
1 голос
/ 02 августа 2020

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

В качестве примера рассмотрим следующую структуру:

typedef struct _list_t {
    int ID;
    char *fmt;
    int nparams;
} list_t;
list_t infoList[100]; //this will be pre-populated with the operations my app offers 


typedef struct _common {
    int ID;
    char *params;
} common;

Функция переменного размера используется для передачи параметров, учитывая, что формат уже заполнен:

int Vfunc(common * c, ...) {
    va_list args;
    va_start(args, c);
    
    //code to search for ID in infoList and fetch its fmt
    char params_buff[100]; //max params is 100
    vsprintf(str_params, fmt, args);

    va_end(args);

    c->params = (char *)malloc(sizeof(char)*(strlen(params_buff)+1));
    strncpy(c->params, params_buff, strlen(params_buff)+1);
}
int execute(common * c) { 
    if (c->ID == 1) { //add 2 numbers
        int x, y; // i expect 2 numbers 
        //code to find ID in infoList and fetch its fmt
        sscanf(c->params, fmt, &x, &y);
    
        return (x + y);
    }
    else if (c->ID == 2) {
    //do another operation, i expect an unsigned char array?
    }
    
}

Основная программа будет выглядеть примерно так:

int main()
{
    common c;
    c.ID = 1;

    Vfunc(&c, 12, 2);
    
    execute(&c);
    
    return 0;
}

Теперь я могу передать структуру для любой функции, которая будет обрабатывать параметры соответствующим образом. Однако я не вижу возможности использовать unsigned char[] в качестве одного из параметров, поскольку массивы беззнаковых символов не имеют «формата». Формат char[] будет %s. В основном я хочу передать некоторые необработанные данные через эту структуру.

Есть ли способ сделать это или лучшую реализацию для достижения цели?

EDIT:

Похоже, цель вопросов неясна. Скажем, мое приложение может выполнять арифметические c операции (например, калькулятор). Скажем, пользователь моего приложения хочет добавить 2 числа. Все, что я хочу, чтобы они сделали, это заполнили эту общую структуру, а затем передали ее, скажем, функции для ее выполнения. Все идентификаторы операций будут известны из, скажем, руководства, поэтому пользователь будет знать, сколько параметров они могут передать и какой идентификатор что делает. Как владелец приложения я буду заполнять infoList идентификаторами, которые я предлагаю.

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

Ответы [ 4 ]

1 голос
/ 17 августа 2020

Насколько я понимаю ваш вопрос, вы хотите сохранить все значения аргументов в текстовой строке, чтобы значения можно было восстановить позже, используя sscanf. Кроме того, вы хотите иметь возможность обрабатывать массив чисел, например массив символов без знака.

И вы спрашиваете:

Есть ли способ сделать это

Для того, чтобы ваша идея работала, требуется, чтобы sscanf мог анализировать (или соответствовать) тип данных, который вы хотите использовать в своей программе. И - как вы пишете в вопросе - scanf не может анализировать массивы чисел. Итак, ответ:

Нет , это невозможно сделать с помощью стандартных функций.

Итак, если вы хотите иметь возможность обрабатывать массивы чисел, вам придется написать свою собственную функцию сканирования. Это включает в себя:

  1. выбор спецификатора преобразования, чтобы указать коду сканирование массива (например,% b),
  2. выбор текстового формата для массива (например, «{1 , 2, 3} ")
  3. способ хранения данных массива и размера, например struct {unsiged char* p; size_t nb_elements;}.

Далее вы такая же проблема с vsprintf. Опять же, вам нужно написать свою собственную функцию.

EDIT

Одна альтернатива (которая мне не очень нравится) - хранить значения указателя. То есть вместо хранения значений массива в строке params вы можете сохранить указатель на массив.

Плюс такого подхода в том, что вы можете использовать стандартные функции.

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

Другими словами:

unsigned char auc[] = {1, 2, 3};
Vfunc(&c, auc);
execute(&c);

подойдет, но

Vfunc(&c, (unsigned char[]){1, 2, 3});
execute(&c);

будет компилироваться, но завершится ошибкой во время выполнения.

И - как всегда с массивами в C - вам может потребоваться дополнительный аргумент для количества элементов массива.

Пример кода для этого Подход "сохранить как указатель" может быть следующим:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>

typedef struct _common {
    int ID;
    char *params;
} common;

void Vfunc(common * c, ...) {
    va_list args;
    va_start(args, c);

    //code to search for ID in infoList and fetch its fmt
    // For this example just use a fixed fmt
    char fmt[] ="%p %zu";


    char params_buff[100]; //max params is 100
    vsprintf(params_buff, fmt, args);

    va_end(args);

    c->params = (char *)malloc(sizeof(char)*(strlen(params_buff)+1));
    strncpy(c->params, params_buff, strlen(params_buff)+1);
}

int execute(common * c)
{
    if (c->ID == 1) {
      // expect pointer and number of array elements
      unsigned char* a;
      size_t nbe;

      //code to find ID in infoList and fetch its fmt
      // For this example just use a fixed fmt
      char fmt[] ="%p %zu";

      if (sscanf(c->params, fmt, &a, &nbe) != 2) exit(1);

      // Calculate average
      int sum = 0;
      for (size_t i = 0; i < nbe; ++i) sum += a[i];
      return sum;
    }

    return 0;
}

int main(void)
{
  common c;
  c.ID = 1;

  unsigned char auc[] = {1, 2, 3, 4, 5, 6};

  Vfunc(&c, auc, sizeof auc / sizeof auc[0]);

  printf("The saved params is \"%s\"\n", c.params);
  printf("Sum of array elements are %d\n", execute(&c));

  return 0;
}

Возможный результат

The saved params is "0xffffcc0a 6"
Sum of array elements are 21

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

0 голосов
/ 21 августа 2020

Я понимаю, что вы спрашиваете о передаче последовательности объектов разного типа в функцию. В качестве особой детали вы хотите, чтобы функция получала только один фактический аргумент, но это не имеет особого значения, потому что всегда можно преобразовать функцию, которая принимает несколько аргументов, в другую, которая принимает только один, путем обертывания нескольких параметров в соответствующей структуре. . Кроме того, я рассматриваю пример использования Vfunc() кода vsprintf() как деталь реализации, а не как важный компонент требуемого решения.

В этом случае, несмотря на мои серьезные сомнения в полезности того, что вы, кажется, хотите, как C упражнение по программированию, кажется, не так уж и сложно. Основная идея c, которую вы, кажется, ищете, называется объединением с тегами . Он также известен под другими названиями, но это хорошо согласуется с соответствующими концепциями и ключевыми словами языка C. Основная идея заключается в том, что вы определяете тип, который может содержать объекты различных других типов, по одному, и который несет дополнительный член, который определяет, какой тип в данный момент принадлежит каждому экземпляру.

Например:

enum tag { TAG_INT, TAG_DOUBLE, TAG_CHAR_PTR };
union tagged {
    struct {
        enum tag tag;
        // no data -- this explicitly gives generic access to the tag
    } as_any;
    struct {
        enum tag tag;
        int data;
    } as_int;
    struct {
        enum tag tag;
        double data;
    } as_double;
    struct {
        enum tag tag;
        char *data;
    } as_char_ptr;
    // etc.
};

Затем вы можете объединить это с простой оболочкой списка:

struct arg_list {
    unsigned num;
    union tagged *args;
};

Затем, учитывая такую ​​функцию, как эта:

int foo(char *s, double d) {
    char[16] buffer;
    sprintf(buffer, "%15.7e", d);
    return strcmp(s, buffer);
}

Затем вы можете обернуть это как так:

union tagged foo_wrapper(struct arg_list args) {
    // ... validate argument count and types ...

    return (union tagged) { .as_int = {
        .tag = TAG_INT, .data = foo(args[0].as_char_ptr.data, args[1].as_double.data)
    } };
}

и вызовите обертку так:

void demo_foo_wrapper() {
    union tagged arg_unions[2] = {
        { .as_char_ptr = { .tag = TAG_CHAR_PTR, .data = "0.0000000e+00" },
        { .as_double =   { .tag = TAG_DOUBLE,   .data = 0.0 }
    };
    union tagged result = foo_wrapper((struct arg_list) { .num = 2, .args = arg_unions});
    printf("result: %d\n", result.as_int.data);
}

Обновление:

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

union varying {
    int as_int;
    double as_double;
    char *as_char_ptr;
    // etc.
};

struct arg_list {
    unsigned num;
    union varying *args;
};

union varying foo_wrapper(struct arg_list args) {
    return (union vaying) { .as_int = foo(args[0].as_char_ptr, args[1].as_double) };
}

void demo_foo_wrapper() {
    union varying arg_unions[2] = {
        .as_char_ptr = "0.0000000e+00",
        .as_double   = 0.0
    };
    union varying result = foo_wrapper((struct arg_list) { .num = 2, .args = arg_unions});
    printf("result: %d\n", result.as_int);
}
0 голосов
/ 18 августа 2020

Я прочитал задачу еще раз и обнаружил, что она намного проще, чем вы описали.

Согласно вашему утверждению, вы уже знаете о типе и порядке извлечения данных в execute() функция. Это значительно упрощает эту проблему.

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

Теперь, при этом, c предоставляет способ обработки аргументов переменной длины. И это преимущество.

Итак, что нам нужно сделать:

  1. кэшировать все аргументы из аргументов переменной длины, т.е. va_list.
  2. и предоставить способ получить предоставленные аргументы из этого кеша.

Сначала я собираюсь показать вам, как получать элементы из кеша, если вы знаете их тип. Сделаем это с помощью макроса. Я назвал его sarah_next(). Что ж, в конце концов, я должна написать это из-за тебя. Вы можете называть его как хотите. Его определение приведено ниже:

#define sarah_next(cache, type)                        \
        (((cache) = (cache) + sizeof(type)),           \
        *((type*) (char *) ((cache) - sizeof(type))))

Итак, простыми словами, sarah_next() извлекает next element из cache и преобразует его в type.

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

void *cache = malloc(sizeof(char) * cacheSize);
// itr is an iterator, which iterates over cache
char *itr = (char *)cache;
// now, you can do
*(type *)itr = va_arg(buf, type);
// and then
itr += sizeof(type);

Другой момент, который я хотел бы обсудить, это то, что я использовал подсказку типа для определения размера кеша. Для этого я использовал функцию getSize(). Вы поймете, если просто посмотрите на него (также обратите внимание: это дает вам возможность использовать свой собственный тип):

// getSize() is a function that returns type size based on type hint
size_t getSize(char type) {
    if(type == 's') {
        return sizeof(char *);
    }
    if(type == 'c') {
        return sizeof(char);
    }
    if(type == 'i') {
        return sizeof(int);
    }
    if(type == 'u') { // 'u' represents 'unsigned char'
        return sizeof(unsigned char);
    }
    if(type == 'x') { // let's, say 'x' represents 'unsigned char *'
        return sizeof(unsigned char *);
    }
    // you can add your own custom type here
    // also note: you can easily use 'unsigned char'
    //            use something like 'u' to represent 'unsigned char'
    //            and you're done
    // if type is not recognized, then
    printf("error: unknown type while trying to retrieve type size\n");
    exit(1);
}

Хорошо, я думаю, идеи завершены. Прежде чем двигаться дальше, попробуйте asp тщательно продумать идеи.

Теперь позвольте мне предоставить полный исходный код:

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>

// note: it is the most fundamental part of this solution
//       'sarah_next' is a macro, that
//       returns *(type *)buf means a value of type "type", and also
//       increments 'buf' by 'sizeof(type)', so that
//       it may target next element
//       'sarah_next' is used to retrieve data from task cache

// I've named it after you, you may choose to name it as you wish
#define sarah_next(cache, type)                        \
        (((cache) = (cache) + sizeof(type)),           \
        *((type*) (char *) ((cache) - sizeof(type))))


// defining pool size for task pool
#define POOL_SIZE 1024


// notice: getSize() has been updated to support unsigned char and unsigned char *
// getSize() is a function that returns type size based on type hint
size_t getSize(char type) {
    if(type == 's') {
        return sizeof(char *);
    }
    if(type == 'c') {
        return sizeof(char);
    }
    if(type == 'i') {
        return sizeof(int);
    }
    if(type == 'u') { // 'u' represents 'unsigned char'
        return sizeof(unsigned char);
    }
    if(type == 'x') { // let's, say 'x' represents 'unsigned char *'
        return sizeof(unsigned char *);
    }
    // you can add your own custom type here
    // also note: you can easily use 'unsigned char'
    //            use something like 'u' to represent 'unsigned char'
    //            and you're done
    // if type is not recognized, then
    printf("error: unknown type while trying to retrieve type size\n");
    exit(1);
}


typedef struct __task {
    int id;
    void *cache;
} Task;

// notice: constructTask has been updated to support unsigned char and unsigned char *
// note: here, types contains type hint
Task *constructTask(int id, char *types, ...) {
    // determine the size of task cache
    int cacheSize = 0;
    for(int i=0; types[i]; i++) {
        cacheSize += getSize(types[i]);
    }
    // allocate memory for task cache
    void *cache = malloc(sizeof(char) * cacheSize);
    
    va_list buf;
    va_start(buf, types);
    
    // itr is an iterator, which iterates over cache
    char *itr = (char *)cache;
    for(int i=0; types[i]; i++) {
        if(types[i] == 's') {
            *(char **)itr = va_arg(buf, char *);

        } else if(types[i] == 'x') { // added support for 'unsigned char *'
            *(unsigned char **)itr = va_arg(buf, unsigned char *);

        } else if(types[i] == 'c') {
            // notice: i used 'int' not 'char'
            // cause: compiler-warning: 'char' is promoted to 'int' when passed through '...'
            // also note: this promotion helps with 'unsigned char'
            *(char *)itr = (char)va_arg(buf, int); // so cast it to char

        } else if(types[i] == 'u') { // added support 'unsigned char'
            // notice: i used 'int' not 'unsigned char'
            // cause: compiler-warning: 'unsigned char' is promoted to 'int' when passed through '...'
            // also note: this promotion helps with 'unsigned char'
            *(unsigned char *)itr = (unsigned char)va_arg(buf, int); // so cast it to unsigned char

        } else if(types[i] == 'i') {
            *(int *)itr = va_arg(buf, int);

        }
        // it won't come to else, cause getSize() would
        // caught the type error first and exit the program
        itr += getSize(types[i]);
    }

    va_end(buf);

    // now, construct task
    Task *task = malloc(sizeof(Task));
    task->id = id;
    task->cache = cache;
    // and return it
    return task;
}

// destroyTask is a function that frees memory of task cache and task
void destroyTask(Task *task) {
    free(task->cache);
    free(task);
}

// notice: that 'task->id == 4' processing part
// it is equivalant to your 'execute()' function
int taskProcessor(Task *task) {
    // define ret i.e. return value
    int ret = 999; // by default it is some code value, that says error

    // note: you already know, what type is required in a task
    if(task->id == 1) {
        // note: see usage of 'sarah_next()'
        int x = sarah_next(task->cache, int);
        int y = sarah_next(task->cache, int);

        ret = x + y;

    } else if(task->id == 2) {
        char *name = sarah_next(task->cache, char *);
        if(strcmp(name, "sarah") == 0) {
            ret = 0; // first name
        } else if (strcmp(name, "cartenz") == 0) {
            ret = 1; // last name
        } else {
            ret = -1; // name not matched
        }
    } else if(task->id == 3) {
        int x = sarah_next(task->cache, int);
        char *name = sarah_next(task->cache, char *);
        int y = sarah_next(task->cache, int);

        printf("%d %s %d\n", x, name, y); // notice: we've been able to retrieve
        // both string(i.e. char *) and int
        // you can also see for ch and int, but i can assure you, it works

        ret = x + y;

    } else if(task->id == 4) { // working with 'unsigned char *'
        int a = sarah_next(task->cache, int);
        unsigned char *x = sarah_next(task->cache, unsigned char *); // cast to unsigned char *
        // char *x = sarah_next(task->cache, char *); // this won't work, would give wrong result
        int b = sarah_next(task->cache, int);

        printf("working with 'unsigned char *':");
        for(int i=0; x[i]; i++) {
            printf(" %d", x[i]); // checking if proper value is returned, that's why using 'integer'
        }
        printf("\n");

        ret = a + b;
    } else {
        printf("task id not recognized\n");
    }

    return ret;
}


int main() {
    Task *taskPool[POOL_SIZE];

    int taskCnt = 0;

    taskPool[taskCnt++] = constructTask(1, "ii", 20, 30); // it would return 50
    taskPool[taskCnt++] = constructTask(1, "ii", 50, 70); // it would return 120
    taskPool[taskCnt++] = constructTask(2, "s", "sarah"); // it would return 0
    taskPool[taskCnt++] = constructTask(2, "s", "cartenz"); // it would return 1
    taskPool[taskCnt++] = constructTask(2, "s", "reyad"); // it would return -1
    taskPool[taskCnt++] = constructTask(3, "isi", 40, "sarah", 60); // it would print [40 sarah 60] and return 100

    // notice: I've added an exmaple to showcase the use of unsigned char *
    // also notice: i'm using value greater than 127, cause
    // in most compiler(those treat char as signed) char supports only upto 127
    unsigned char x[] = {231, 245, 120, 255, 0}; // 0 is for passing 'NULL CHAR' at the end of string
    // 'x' is used to represent 'unsigned char *'
    taskPool[taskCnt++] = constructTask(4, "ixi", 33, x, 789); // it would print ['working with unsigned char *': 231 245 120 255] and return 822
    // note: if you used 'char *' cast to retrieve from 'cache'(using a compiler which treats char as signed), then
    //       it would print [-25 -11 120 -1] instead of [231 245 120 255]
    //       i guess, that makes it clear that you can perfectly use 'unsigned char *'

    for(int i=0; i<taskCnt; i++) {
        printf("task(%d): %d\n", i+1, taskProcessor(taskPool[i]));
        printf("\n");
    }

    // at last destroy all tasks
    for(int i=0; i<taskCnt; i++) {
        destroyTask(taskPool[i]);
    }

    return 0;
}

Результат:

// notice the updated output
task(1): 50                                        
                                                   
task(2): 120                                       
                                                   
task(3): 0                                         
                                                   
task(4): 1                                         
                                                   
task(5): -1                                        
                                                   
40 sarah 60                                        
task(6): 100                                       
                                                   
working with 'unsigned char *': 231 245 120 255    
task(7): 822

Итак, вам может быть интересно, какое преимущество это может дать вашему данному решению. Во-первых, вам не обязательно использовать %s %d et c. определите формат, который нелегко изменить или создать для каждой задачи, и вы можете написать много (для каждой задачи вам, возможно, придется писать разные fmt), и вы не используете vsprintf et c ... который имеет дело только со встроенными типами.

И второй важный момент - вы можете использовать свой собственный custom type. Объявите свой собственный struct type, и вы сможете его использовать. И это также легко add new type.

update:

Я забыл упомянуть еще одно преимущество, вы также можете использовать с ним unsigned char . видите, обновленная функция getSize(). вы можете использовать символ 'u' для unsigned char, а поскольку unsigned char повышается до int, вы можете просто преобразовать его в (unsigned char) и сделать ...

update-2 (поддержка unsigned char *):

Я обновил код для поддержки unsigned char и unsigned char *. Для поддержки нового типа вам необходимо обновить функции getSize() и constructTask(). Сравните предыдущий код и недавно обновленный код ... вы поймете, как добавлять новые типы (вы также можете добавлять свои собственные типы).

Также обратите внимание на task->id == 4 часть в taskProcessor() функция. Я добавил это, чтобы продемонстрировать использование unsigned char *. Надеюсь, это все проясняет.

Если у вас есть вопросы, задавайте их в комментариях ...

0 голосов
/ 17 августа 2020

Я думаю, что то, что вы хотите от своего описания, скорее всего, представляет собой объединение структур, при этом 1-й член объединения является перечислителем, который определяет тип используемой структуры, именно так полиморфизм часто достигается в C. Взгляните на заголовки X11, чтобы увидеть гигантский пример Xevents

Тривиальный пример:

//define our types
typedef enum  {
chicken,
cow,
no_animals_defined
} animal;

typedef struct {
animal species;
int foo;
char bar[20];
} s_chicken;

typedef struct {
animal species;
double foo;
double chew;
char bar[20];
} s_cow;

typedef union {
animal species; // we need this so the receiving function can identify the type.
s_chicken chicken ;
s_cow cow ;
} s_any_species;

Теперь эта структура может быть передана функции и примет любой идентификатор. Функция-получатель типа s_any_species может выполнять отмену ссылки.

void myfunc (s_any_species any_species)
{
if (any_species.species == chicken)
   any_species.chicken.foo=1 ;
}

Массивы указателей функций предпочтительнее длинных последовательностей if else здесь, но любой из них будет работать

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...