определить элемент массива по ссылке в C - PullRequest
1 голос
/ 21 января 2020

Я переписываю функцию (она работает, так что я, очевидно, хочу ее сломать), которая перебирает 2-D массив. Функция будет читать INI-файл для содержимого данного ключа и либо сохранять значение, возвращаемое в переменную, либо сохранять значение по умолчанию для переменной (в моей структуре переменная определяется ссылкой как * pStoreToRef).

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

Двумерный массив набирается структурой:

typedef struct s_defaults {
    char        *cKeyTitle;
    uint16_t    nItemNr;
    uint8_t     nVarType;
    int         *pDefaultValue;
    uint16_t    nVarLength;
    uint8_t     nVarFloatDecimals;
    char        *pFormatString;
    int         *pStoreToRef;
    int         nNextItem;  // set to -1 at end
} site_defaults_t;

Вот инициализатор:

site_defaults_t site_defaults[] =
{
    {"AUTOTUN_TOTAL_CYCLES\0",          SD_AUTORUN_TOTAL_CYCLES,            VT_INTEGER, 192,                            5,  0,  "%i\0",     (int*)&aAutoRunControl.nTotalCycles,1},
    {"AUTORUN_TEST_INTERVAL\0",         SD_AUTORUN_TEST_INTERVAL,           VT_INTEGER, 60,                             5,  0,  "%i\0",     (int*)&aAutoRunControl.nTestIntervalSecs,2},
    {"AUTORUN_TEST_LEN_SECS\0",         SD_AUTORUN_TEST_LEN_SECS,           VT_INTEGER, 30,                             6,  0,  "%i\0",     (int*)&aAutoRunControl.nTestLengthSecs,3},
    {"AUTORUN_TEST_FAN_SPEED\0",        SD_AUTORUN_TEST_FAN_SPEED,          VT_INTEGER, 85,                             3,  0,  "%i\0",     (int*)&aAutoRunControl.nSpeed,4},
    {"AUTORUN_TEST_PLATE_VOLTS\0",      SD_AUTORUN_TEST_PLATE_VOLTS,        VT_FLOAT,   40,                             3,  1,  "%2.1f\0",  (int*)&aAutoRunControl.vPlate,5},
    {"AUTORUN_TEST_SAMPLES_PER_SEC\0",  SD_AUTORUN_TEST_SAMPLES_PER_SEC,    VT_INTEGER, 10,                             2,  0,  "%i\0",     (int*)&aAutoRunControl.nSamplesPerSecond,6},
    {"SEND_FILES_FREQUENCY\0",          SD_SEND_FILES_FREQUENCY,            VT_INTEGER, 10,                             6,  0,  "%i\0",     (int*)&oSystemInfo.nSendFilesFrequency,7},
    {"POWER_60_HZ\0",                   SD_POWER_60_HZ,                     VT_BOOLEAN, true,                           1,  0,  "%i\0",     (int*)&aSystemState.Power60Hz,8},
    {"UPLOAD_ON_BOOT\0",                SD_UPLOAD_ON_BOOT,                  VT_BOOLEAN, false,                          1,  0,  "%i\0",     (int*)&oSystemInfo.EnableSendFilesOnBoot,9},
    {"AUTORUN_SENDFILES\0",             SD_AUTORUN_SENDFILES,               VT_BOOLEAN, true,                           1,  0,  "%i\0",     (int*)&aAutoRunControl.sendFiles,10},
    {"PREAMP_TRIM\0",                   SD_PREAMP_TRIM,                     VT_FLOAT,   0,                              3,  1,  "%2.1f\0",  (int*)&aSystemState.nPreampTrim,11},
    {"AUTORUN_PCIL_CONVERSION_CONST\0", SD_AUTORUN_PCIL_CONVERSION_CONST,   VT_FLOAT,   0.07608,                        2,  8,  "%2.8f\0",  (int*)&aAutoRunControl.pcilMult,12},
    {"ENABLE_DVDT_BEEP\0",              SD_ENABLE_DVDT_BEEP,                VT_BOOLEAN, false,                          1,  0,  "%i\0",     (int*)&oSystemInfo.AllowDvDtBeep,13},
    {"AUTORUN_PCIL_CONVERSION_ADJUST\0",SD_AUTORUN_PCIL_CONVERSION_ADJUST,  VT_FLOAT,   0.02,                           2,  8,  "%2.8f\0",  (int*)&aAutoRunControl.pcilMultAdjust,14},
    {"AUTORUN_DEMO_DURATION\0",         SD_AUTORUN_DEMO_DURATION,           VT_INTEGER, 30,                             5,  0,  "%i\0",     (int*)&aAutoRunControl.nDemoDuration,-1},
};

Новое поле, int * pDefaultValue; проблематично c. Моя теория заключалась в том, чтобы хранить ссылку на значение, и таким образом хранимое pDefaultValue может быть любого типа. Это должно сработать, если к значению по умолчанию для хранения можно сослаться на адрес, но в этом случае?

Есть какие-нибудь идеи относительно обходного пути?

Ответы [ 4 ]

0 голосов
/ 21 января 2020

Вот одно из многих возможных решений. Поскольку вы используете VARIANT для описания информации, давайте использовать VARIANT.

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

Сначала расширенная структура для этого примера:

typedef struct SiteMeta {
    char* name;
    VARIANT v;       
};

Определить функцию для инициализации VARIANT.

VARIANT VariantValInit(const VARTYPE vt, const void* value) {
    VARIANT v;
    VariantInit(&v);
    v.vt = vt;
    switch (vt) {
    case VT_I4:
        v.intVal = (int)value;
        break;
    case VT_BOOL:
        v.boolVal = (bool)value;
        break;
    default:
        // throw or something.
        break;
    }
    return v;
}

Конечно, для выполнения вышеприведенной функции потребуются дополнительные регистры для поддержки других типов.

Теперь объявите ваши значения по умолчанию следующим образом:

SiteMeta site_defaults[] =
{
    { "AUTOTUN_TOTAL_CYCLES", VariantValInit(VT_I4, (void*)192) },
    {"POWER_60_HZ", VariantValInit(VT_BOOL, (void*)true) }
};
0 голосов
/ 21 января 2020

Самый простой и не очень удачный подход - объединить его:

typedef struct s_defaults {
    uint8_t     nVarType;
    union some_union_name_in_case_you_dont_have_c11 {
        float f;
        int i;
        bool b;
    } pDefaultValue;
};

struct s_defaults site_defaults[] = {
      { VT_INTEGER, { .i = 192 }, },
      { VT_FLOAT, { .f = 1.92 }, },
      { VT_BOOL, { .b = true }, },
};

Это приводит к многостраничным переключающим операторам для обработки каждого типа.

Более лучший способ использовать указатели на функции и корректную перегрузку интерфейса.

struct s_default_vtable {
     void (*assign)(void *to, const void *value, void *cookie);
     void (*print)(const void *value, const char *fmt, void *cookie);
};


void assign_float(void *to, const void *value, void *cookie) {
    *(float*)to = *(const float*)value;
}
void print_float(const void *value, const char *fmt, void *cookie) {
   printf(fmt, *(const float*)value);
}

const struct s_default_vtable s_default_vtable_float = {
   .assign = assign_float,
   .print = print_float,
};
// similar for int and bool types

typedef struct s_defaults {
    const struct s_default_vtable *v; // vtable pointer
    void *cookie; // user context to pass to vtable functions
    const void *pDefaultValue;
    const char *pFormatString; // as this is tied to the type,
                               // I think it should be moved into vtable
                               // or just hardcoded into print function
    void *pStoreToRef;
} site_defaults_t;

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

// example for float type
float number_to_store_result_to;
const float float_default_value = 50.0;
struct s_defaults defaults = {
    .v = &s_default_vtable_float ,
    .cookie = 0,
    .default_value = &float_default_value.
    .pFormatString = "%f",
    .pStoreToRef = &number,
};

// assign default value to pStoreToReg
// no need for switch, no need for anything
defaults.v->assign(defaults.pStoreToReg, defaults.default_value, defaults.cookie);

Примечания:

  • Строковые литералы включают нулевой завершающий символ. \0" не требуется (если только вы действительно не хотите иметь два нулевых символа в конце строки).

  • Члены:

    uint16_t    nVarLength;
    uint8_t     nVarFloatDecimals;
    char        *pFormatString;
    

Rigth теперь дублирует информацию в вашей структуре. Вам нужен только спецификатор формата для типа, например, для float f. %<this>.<this> может быть собран динамически с небольшим буфером stati c.

void print_float(int nVarLength, int nVarFloatDecimals, const char *pFormatString, float *value) {
        char buf[100];
        snprintf(buf, sizeof(buf), "%%%d.%d%s", nVarLength, nVarFloatDecimals, pFormatString);
        printf(buf, *(float*)value);
}
// call like:
print_float(2, 8, "f", 1.0);
0 голосов
/ 21 января 2020

Строковые литералы, такие как "AUTOTUN_TOTAL_CYCLES", неявно включают нулевой терминатор. Таким образом, "AUTOTUN_TOTAL_CYCLES\0" фактически размещает два NULL-терминатора, один неявный, а другой явный. Это необязательно.

Кроме того, поскольку вы инициализируете struct инициализатором, type первого члена можно изменить на: const char *cKeyTitle;

Далее, код как есть, создает сообщение об ошибке "неожиданное имя типа" VT_INTEGER "(и аналогичное для других имен типов) для инициализатора структуры site_defaults_t site_defaults[]

Это связано с тем, что C type нельзя использовать в качестве значения Это будет похоже на следующее:

struct {
    int int;
    float float;
    ...

Ниже приведена копия исходного кода в уменьшенном масштабе, чтобы показать, как вы можете решить свою проблему, заменив void * для создания гибкого введите использование объединения:

Это простой пример, но включает функцию main, чтобы вы могли ее построить и поэкспериментировать, чтобы увидеть, как работает union ...

typedef enum {
SD_AUTORUN_TOTAL_CYCLES,                
SD_AUTORUN_TEST_INTERVAL,           
SD_AUTORUN_TEST_LEN_SECS,               
SD_AUTORUN_TEST_FAN_SPEED,              
SD_AUTORUN_TEST_PLATE_VOLTS,            
SD_AUTORUN_TEST_SAMPLES_PER_SEC,        
SD_MAX //count of items in enum list
}TASK;

typedef enum {
    V_INTEGER,//replace VT_INTEGER in initializer
    V_FLOAT,
    V_BOOLEAN,
}TYPES;

typedef union {
    int iVar;
    float fVar;
    bool bVar
}U_TYPE;

typedef struct s_defaults {
    char        *cKeyTitle;
    uint16_t    nItemNr;
    uint8_t     nVarType;
    U_TYPE      uVar;//union of types
 } site_defaults_t;




site_defaults_t site_defaults[] =
{
    {"AUTOTUN_TOTAL_CYCLES",          SD_AUTORUN_TOTAL_CYCLES,            V_INTEGER, 2},
    {"AUTORUN_TEST_INTERVAL",         SD_AUTORUN_TEST_INTERVAL,           V_INTEGER, 8},
    {"AUTORUN_TEST_LEN_SECS",         SD_AUTORUN_TEST_LEN_SECS,           V_INTEGER, 20},
    {"AUTORUN_TEST_FAN_SPEED",        SD_AUTORUN_TEST_FAN_SPEED,          V_INTEGER, 0},
    {"AUTORUN_TEST_PLATE_VOLTS",      SD_AUTORUN_TEST_PLATE_VOLTS,        V_FLOAT,   123.56},
    {"AUTORUN_TEST_SAMPLES_PER_SEC",  SD_AUTORUN_TEST_SAMPLES_PER_SEC,    V_INTEGER, 15}

 };

int main(void)
{
    return 0;
}
0 голосов
/ 21 января 2020

Я считаю, что ваш лучший выбор - иметь двух членов в этой структуре вместо pDefaultValue. Первый - это строковая форма значения по умолчанию, скажем, pDefaultValueStr. Другой - pDefaultValue, который должен быть объявлен void * и в котором значение по умолчанию хранится во внутренней форме: то, которое будет приведено, когда функция получения должна получить его.

Вы бы затем выполнить функцию заполнения инициализатора в начале выполнения программы, которая заполняет pDefaultValue с учетом содержимого pDefaultValueStr и switch после nVarType.

Инициализатор, site_defaults, будет иметь нулевой указатель присваивается каждому элементу pDefaultValue и будет заменено на пригодное для использования, доступное значение с помощью функции заполнения инициализатора, или оно будет иметь значение "d ie", если значение не может быть преобразовано из строки во внутреннее представление.

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

...