Используйте printf для аргументов тернарного оператора со смешанными типами

У меня каверзный, но интересный вопрос. Повесить там!

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

Теперь давайте рассмотрим эту структуру:

typedef struct{

   // pointer to a function that computes the "real" value
   float (*getRealValue)(void);

   // pointer to a function that computes a "stringified" value
   bool (*getCustomString)(char *);

} var_info_t;

И давайте объявим одну переменную var1, которая является температурой:

uint16_t var1 = 1530; // means 15.3°C

float var1_toFloat(void){ return (float)var1/100; } // should return 15.3

var_info_t var1_info = { .getRealValue = var1_toFloat, .getCustomString = NULL };

и другая переменная var2, которая больше похожа на логическое значение:

uint16_t var2 = 1; // means Enabled

bool var2_toString(char* buffer){  // should write "Enabled"
   if(buffer) sprintf(buffer, "%s", var2 ? "Enabled" : "Disabled");
   return true;
}
var_info_t var2_info = { .getRealValue = NULL, .getCustomString = var2_toString };

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

(Мне просто нужно позвонить TEXT_SetText(int widget_id, char* text) обновить мои виджеты GUI.)

Я хочу создать "обертку" для TEXT_SetText(), как это...

static char text[128], buf[2][32];

#define GET_VAR_AUTO(var_info, i) \
  ((var_info.getCustomString != NULL && var_info.getCustomString(buf[i])) ? buf[i] : \
   (var_info.getRealValue != NULL ? var_info.getRealValue() : 0))

void my_TEXT_SetText(int widget_id, char* format, var_info_t a, var_info_t b){
   snprintf(text, sizeof(text), format, GET_VAR_AUTO(a, 0), GET_VAR_AUTO(b, 1));
   TEXT_SetText(widget_id, text);
}

... так что зовет my_TEXT_SetText(0, "Regulator is %s at %.1f\260C", var2_info, var1_info);

будет отображать хороший Regulator is Enabled at 15.3°C внутри моей коробки

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

Проблема: макрос GET_VAR_AUTO не является синтаксически правильным, потому что он смешивается char* от buf[i] а также float от getRealValue() в возможных результатах троичного оператора:

ошибка: несоответствие типов в условном выражении

НО, зная, что sprintf является переменной функцией, которая обрабатывает свои аргументы в соответствии с типами, указанными в строке формата (%f, %s, ... благодаря va_arg() функция), есть ли способ найти общий абстрактный тип для троичного оператора, который был бы принят sprintf?

Я перепробовал много вещей, но я не могу получить char* а также float работать одновременно.

(К сожалению, я использую C, а не C++)

2 ответа

Решение

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

Например, аргументы с плавающей точкой передаются в регистрах с плавающей точкой /SSE, например, в x86-64, тогда как целочисленные аргументы в целочисленных регистрах. Нет способа передать переменный аргумент, который передается как "оба" в переменных аргументах, потому что в вызове функции, как

printf("%f %d",  f,  d  );
       R1        F1  R2

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

printf("%d %f",  d,  f  );
       R1        R2  F1

они будут переданы в те же регистры снова!

В самом деле,

#include <stdio.h>
int main(void) {
    printf("%f %s\n", "hello world", 2.5);
}

скомпилированный с GCC для x86-64/Linux, и запустить, не будет печатать случайный мусор, но точно 2.50000 hello world, Таким образом, на x86-64 самое большее, что вы могли бы сделать, это передать последний аргумент в регистре с плавающей запятой и в регистре общего назначения.


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

Это все еще будет довольно сложно.

Если вы действительно в отчаянии, подумайте о том, чтобы попробовать libffi.

Определять

#define FLOAT_STR_SIZE_MAX (32) /* or what ever you feel suits */
#define FTOA(b, s, x) \
  (snprintf(b, s, "%f", x), b)

и заменить

 (var_info.getRealValue != NULL ? var_info.getRealValue() : 0)

от

 FTOA((char[FLOAT_STR_SIZE_MAX]){'\0'},
   FLOAT_STR_SIZE_MAX,
   (var_info.getRealValue != NULL ? var_info.getRealValue() : 0.))

:-)

Другие вопросы по тегам