C 是否有一个“问题”;空";不使用';不打印任何内容,用于跳过参数?

C 是否有一个“问题”;空";不使用';不打印任何内容,用于跳过参数?,c,text,formatting,printf,output,C,Text,Formatting,Printf,Output,如果我想要一个程序有多种文本输出格式,我可以这样做: 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 =

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

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 *fmtNoQuantity = "%ZThe price of %s is $%.2f each.";
const char *fmtNoQuantity = "The price of %2$s is $%3$.2f each.";

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;
    }
}
由于价格是最后指定的,我还可以添加一个不列出价格的:

const char *fmtNoPrices = "%u x %s\n";
但是如果我想省略数量呢?如果我这样做:

const char *fmtNoQuantity = "The price of %s is $%.2f each.\n";
然后将发生未定义的行为(很可能是segfault),而不是我想要的。这是因为它会将第一个参数视为指向字符串的指针,即使它实际上是一个无符号int。此无符号int很可能指向有效字符串数据以外的内容,或者(更可能的是,特别是如果您没有购买数亿个同一项)无效的内存位置,导致分割错误

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

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 *fmtNoQuantity = "%ZThe price of %s is $%.2f each.";
const char *fmtNoQuantity = "The price of %2$s is $%3$.2f each.";

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;
    }
}

事实上,我是在为我的问题寻找答案的时候自己弄明白的。您可以在
%
后面的格式代码前面加上一个参数编号,后跟一个
$
。所以它是这样的:

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 *fmtNoQuantity = "%ZThe price of %s is $%.2f each.";
const char *fmtNoQuantity = "The price of %2$s is $%3$.2f each.";

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;
    }
}
也就是说,字符串将使用第二个参数,浮点将使用第三个参数。 但是,请注意,这是POSIX扩展,不是C的标准特性

更好的方法可能是定义自定义打印函数。大概是这样的:

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 *fmtNoQuantity = "%ZThe price of %s is $%.2f each.";
const char *fmtNoQuantity = "The price of %2$s is $%3$.2f each.";

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;
    }
}

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

您可以通过以下方式达成通用解决方案:

如果可能,请重新排列,使
非-%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中使用了一些示例,谁知道在其他编译器或将来会使用这些示例。)


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

此扩展的一个问题是,引用参数而不引用之前的所有参数会导致未定义的行为,因为
printf
函数不知道这些参数的大小。我知道没有“跳过此参数”格式,所以这甚至不能用来解决这个问题…scanf()可以使用星号,但IIRC printf()不能。我也尝试过使用
.0
精度说明符,但这似乎只适用于字符串(
%.0s
将不显示任何内容,但如果不为null,可能仍会取消对指针的引用)。我认为,您应该使用具有单独参数列表的单独调用集。否则会使国际化(I18N)更加困难。小心国际化(I18N)。英语中结合良好的句子片段在翻译中可能是一场灾难。就国际化问题达成一致。此外,鉴于固定的值顺序,我们有可能更好地将值重新排列在不同的区域性中,这超出了现有的解决方案。
%1$s
符号可以重新排列(甚至重用)参数,但为了安全起见,您必须使用1..N中的每个参数(因为不同类型需要
printf()
等使用堆栈上不同的空间量。例如,
double
需要8个字节,其中
int
通常只需要4个字节。因此,如果堆栈上有
int
double
,则
printf()
必须被告知如何推进构成所有这些内容基础的
vau列表。这并不能阻止攻击者使用
n$
数字中缺少数字的格式字符串攻击,但只要对他们有效,他们就不会那么挑剔。