【问题标题】:Use printf for ternary operator arguments with mixed types将 printf 用于混合类型的三元运算符参数
【发布时间】:2019-05-14 21:36:29
【问题描述】:

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

假设我编写嵌入式代码,并且我在一个系统中,我必须以 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 宏在语法上不正确,因为它在三元运算符的可能结果中混合了来自buf[i]char* 和来自getRealValue()float

错误:条件表达式中的类型不匹配

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

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

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

【问题讨论】:

  • 真的很难理解你在这里想要做什么,但这听起来像是Generic selection的工作
  • 我知道这很难,但我找不到更容易解释的方法。是的,我必须澄清我使用的是 C,而不是 C++... :(
  • @EugeneSh。我不相信仅仅通过泛型选择是可能的...考虑到 OP 希望支持具有任何数量和可能多种类型的函数调用...
  • Motla,IMO,打印对变量或其值“一无所知”的目标与使用?: 的特定方法相关联,并在下面回答了问题。 OTOH,如果您愿意通过另一种方法实现“一无所知”,请考虑_Generic。示例:Formatted print without the need to specify type matching specifiers.
  • @Motla 我已经编辑了我的答案,以更好地突出为什么无法在 x86-64 上进行这项工作;)

标签: c types printf conditional-operator mixed


【解决方案1】:

定义

#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.))

:-)

【讨论】:

  • 感谢您的回答,但我无法正常工作。这个想法是将浮点数转换为 char* ? snprintf() 写入后 (char[FLOAT_STR_SIZE_MAX]){'\0'} 去哪儿了?我必须在我的格式变量中将 %f 更改为 %s 吗?
  • @Motla 是的,您的最终转换说明符必须是 's'
  • 好的,在这种情况下,忘记 getRealValue() 函数更简单,只在任何地方使用字符串函数,忘记混合类型......不回答原来的问题,但我想这就是我无论如何都会去做。谢谢你的帮助!!
  • @Motla 化合物会一直留在堆栈中,直到离开当前范围。
【解决方案2】:

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

例如,浮点参数在 x86-64 上的浮点/SSE 寄存器中传递,而整数参数在整数寄存器中传递。没有办法传递在变量参数中作为“两者”传递的变量参数,因为在函数调用中像

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 for x86-64/Linux 编译并运行,不会打印任何随机垃圾,而是准确地打印 2.50000 hello world。因此,在 x86-64 上,您最多可以做的就是在浮点和通用寄存器中传递 last 参数。


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

这仍然会很棘手。

如果你真的很绝望,可以考虑试试libffi

【讨论】:

  • 好吧 :) 很好的答案,谢谢!我会等待一些奇迹,但如果不来,我会认为它已被接受。
  • "你可以拥有自己的类似 printf 的函数”?
猜你喜欢
  • 2014-08-23
  • 2012-12-15
  • 2011-12-17
  • 2021-05-10
  • 2015-02-04
  • 2017-08-24
  • 1970-01-01
  • 2014-02-08
  • 2012-02-13
相关资源
最近更新 更多