将 printf 用于混合类型的三元运算符参数

Use printf for ternary operator arguments with mixed types

我有一个棘手但有趣的问题。坚持住!

假设我编写嵌入式代码,并且我在一个系统中,我必须以 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 小部件。)

我想要完成的是创建一个 "wrapper" 到 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 %.1f0C", var2_info, var1_info);

会在我的盒子里显示一个漂亮的 Regulator is Enabled at 15.3°C

这里的真正优势是您可以实时更新文本 只需定期从 "anywhere" 调用相同的函数(没有 "knowing anything"关于变量或其值)。您也可以通过增加 snprintf 参数的数量来简单地扩展同一文本中的变量数量。

问题: GET_VAR_AUTO 宏在语法上不正确,因为它混合了来自 buf[i]char* 和来自 float getRealValue()在三元运算符的可能结果中:

error: type mismatch in conditional expression

但是, 知道 sprintf 是一个 可变参数函数 ,它根据格式字符串(%f、%s、...感谢 va_arg() 函数),有没有办法找到三元运算符 的通用抽象类型,这将是被 sprintf 接受 ?

我尝试了很多方法,但无法让 char*float 同时工作。

(不幸的是我使用的是 C,而不是 C++)

不,根本没有任何泛型类型,除非在非常特殊的情况下,否则不能在 C 中完成。

例如,浮点参数在 x86-64 上的浮动 point/SSE 寄存器中传递,而整数参数在整数寄存器中传递。无法传递在变量参数中作为 "both" 传递的变量参数,因为在像

这样的函数调用中
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 上,您最多可以做的是在浮点和通用寄存器中传递 last 参数。


你可以做的是编写你自己的 printf-like 函数,它会接受 指向 void 的指针参数,然后根据实际类型取消引用它们。

还是会比较棘手

如果你真的很绝望,可以考虑尝试 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]){'[=12=]'},
   FLOAT_STR_SIZE_MAX,
   (var_info.getRealValue != NULL ? var_info.getRealValue() : 0.))

:-)