【问题标题】:Is there a "null" printf code that doesn't print anything, used to skip a parameter?是否有一个“空” printf 代码不打印任何内容,用于跳过参数?
【发布时间】:2024-01-07 02:33:01
【问题描述】:

如果我想让一个程序有多种文本输出格式,我可以这样做:

const char *fmtDefault = "%u x %s ($%.2f each)\n";
const char *fmtMultiLine = "Qty: %3u\nItem: %s\nPrice per item: $%.2f\n\n";
const char *fmtCSV = "%u,%s,%.2f\n";

const char *fmt;
switch (which_format) {
    case 1: fmt = fmtMultiLine; break;
    case 2: fmt = fmtCSV; break;
    default: fmt = fmtDefault;
}

printf(fmt, quantity, item_description, price);

由于价格是最后指定的,我还可以添加一个不列出价格的:

const char *fmtNoPrices = "%u x %s\n";

但是如果我想省略数量呢?如果我这样做:

const char *fmtNoQuantity = "The price of %s is $%.2f each.\n";

然后会发生未定义的行为(很可能是段错误),而不是我想要的。这是因为它将第一个参数视为指向字符串的指针,即使它实际上是一个无符号整数。这个 unsigned int 很可能指向有效字符串数据以外的其他内容,或者(更有可能,特别是如果您不购买数亿件相同商品)无效的内存位置,从而导致分段错误。

我想知道是否有一个代码可以放在某处(在此示例中为%Z)来告诉它跳过该参数,如下所示:

const char *fmtNoQuantity = "%ZThe price of %s is $%.2f each.";

【问题讨论】:

  • scanf() 可以使用星号,但 IIRC printf() 不能。我也尝试过使用.0 精度说明符,但这似乎只适用于字符串(%.0s 将不显示任何内容,但如果不为空,可能仍会取消引用指针)
  • 我认为,您应该使用带有单独参数列表的单独调用集。否则会使国际化 (I18N) 变得更加困难。

标签: c text formatting printf output


【解决方案1】:

对于%s 值,有一个“null” printf() 代码:%.0s

您可以通过以下方式找到通用解决方案:

如果可能,重新排列以使non-%s 值在最后,然后在下方指定格式字符串。

我最喜欢的是有 3 个单独的 printf() 调用,每个值使用自己的格式一个。当不需要该值时,只需提供不带说明符的格式字符串。

const char * Format1q   = "";
const char * Format1id  = "The price of %s";
const char * Format1p   = " is $%.2f each.\n";
...
printf(Format1q,  quantity); 
printf(Format1id, item_description);
printf(Format1p,  price);

奇怪的解决方案:

对于其他大小相同的值,您可以尝试也使用%.0s 的未定义行为。 (在 gcc 4.5.3 中使用了一些示例,谁知道其他编译器或未来。)

对于其他 N x 大小与指针大小相同的值,您可以尝试也使用 %.0s N 次的未定义行为。 (在 gcc 4.5.3 中使用了一些示例,谁知道其他编译器或未来。)

【讨论】:

  • 小心国际化 (I18N)。在英语中结合得很好的句子片段可能是翻译的灾难。
  • 同意国际化问题。此外,鉴于价值观的固定顺序,我们认为重新安排价值观可能会更好,这超出了所提供的解决方案。
  • %1$s 表示法可以重新排序(甚至重用)参数,但是您必须使用 1..N 中的每个参数以确保安全(因为不同的类型需要 printf() 等使用不同的堆栈上的空间量。例如,double 需要 8 个字节,而 int 通常只需要 4 个字节。因此,如果堆栈上有 intdoubleprintf() 有被告知如何推进作为所有这些东西的基础的va_list。这并不能阻止攻击者使用格式字符串攻击在n$ 数字中缺少数字,但只要它适用于他们,它们就不会那么挑剔.
【解决方案2】:

我实际上是在为我的问题寻找一些东西时自己想出来的。您可以在% 后面附加一个参数号,后跟$ 到格式代码。所以它会是这样的:

const char *fmtNoQuantity = "The price of %2$s is $%3$.2f each.";

也就是说,字符串使用第二个参数,浮点数使用第三个参数。 但是请注意,这是 POSIX 扩展,而不是 C 的标准功能。

更好的方法可能是定义自定义打印功能。像这样的:


typedef enum {fmtDefault, fmtMultiLine, fmtCSV, fmtNoPrices, fmtNoQuantity} fmt_id;

void print_record(fmt_id fmt, unsigned int qty, const char *item, float price)
{
    switch (fmt) {
    case fmtMultiLine:
        printf("Qty: %3u\n", qty);
        printf("Item: %s\n", item);
        printf("Price per item: $%.2f\n\n", price);
        break;
    case fmtCSV:
        printf("%u,%s,%.2f\n", qty, item, price);
        break;
    case fmtNoPrices:
        printf("%u x %s\n", qty, item);
        break;
    case fmtNoQuantity:
        printf("The price of %s is $%.2f each.\n", item, price);
        break;
    default:
        printf("%u x %s ($%.2f each)\n", qty, item, price);
        break;
    }
}

【讨论】:

  • 这个扩展的一个问题是引用一个参数而不引用它之前的所有参数会导致未定义的行为,因为printf 函数不知道这些参数的大小。而且没有我所知道的“跳过此参数”格式,所以这甚至不能用来解决这个问题......
最近更新 更多