Используйте 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.))
:-)