检查 2 个printf()格式字符串是否兼容是格式解析中的一项练习。
C 至少没有标准的运行时比较函数,例如:
int format_cmp(const char *f1, const char *f2); // Does not exist
"%d %f"和之类的格式"%i %e"显然是兼容的,因为它们都期望int和float/double。注意:float被提升为doubleasshort并signed char被提升为int.
格式"%*.*f"和"%i %d %e"是兼容的,但并不明显:两者都需要int,int和float/double.
格式"%hhd"和"%d"两者都期望一个int,即使第一个将signed char在打印之前将其值转换为。
格式不兼容。"%d"_ 尽管许多系统会按预期运行。注意:通常会升级为."%u"charint
格式"%d"和"%ld"不严格兼容。在 32 位系统上,有等价的,但不是一般的。当然可以更改代码以适应这一点。OTOH"%lf"并且由于to的通常参数提升而"%f" 兼容。floatdouble
格式"%lu"和"%zu" 可能兼容,但这取决于 和 的unsigned long实现size_t。代码的添加可以允许这个或相关的等价。
修饰符和说明符的某些组合未定义为"%zp". 以下内容不允许此类深奥的组合 - 但会比较它们。
修饰符 like"$"是对标准 C 的扩展,在下文中未实现。
的兼容性测试printf()不同于scanf().
#include <ctype.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
typedef enum {
type_none,
type_int,
type_unsigned,
type_float,
type_charpointer,
type_voidpointer,
type_intpointer,
type_unknown,
type_type_N = 0xFFFFFF
} type_type;
typedef struct {
const char *format;
int int_queue;
type_type type;
} format_T;
static void format_init(format_T *state, const char *format);
static type_type format_get(format_T *state);
static void format_next(format_T *state);
void format_init(format_T *state, const char *format) {
state->format = format;
state->int_queue = 0;
state->type = type_none;
format_next(state);
}
type_type format_get(format_T *state) {
if (state->int_queue > 0) {
return type_int;
}
return state->type;
}
const char *seek_flag(const char *format) {
while (strchr("-+ #0", *format) != NULL)
format++;
return format;
}
const char *seek_width(const char *format, int *int_queue) {
*int_queue = 0;
if (*format == '*') {
format++;
(*int_queue)++;
} else {
while (isdigit((unsigned char ) *format))
format++;
}
if (*format == '.') {
if (*format == '*') {
format++;
(*int_queue)++;
} else {
while (isdigit((unsigned char ) *format))
format++;
}
}
return format;
}
const char *seek_mod(const char *format, int *mod) {
*mod = 0;
if (format[0] == 'h' && format[1] == 'h') {
format += 2;
} else if (format[0] == 'l' && format[1] == 'l') {
*mod = ('l' << CHAR_BIT) + 'l';
format += 2;
} else if (strchr("ljztL", *format)) {
*mod = *format;
format++;
} else if (strchr("h", *format)) {
format++;
}
return format;
}
const char *seek_specifier(const char *format, int mod, type_type *type) {
if (strchr("di", *format)) {
*type = type_int;
format++;
} else if (strchr("ouxX", *format)) {
*type = type_unsigned;
format++;
} else if (strchr("fFeEgGaA", *format)) {
if (mod == 'l') mod = 0;
*type = type_float;
format++;
} else if (strchr("c", *format)) {
*type = type_int;
format++;
} else if (strchr("s", *format)) {
*type = type_charpointer;
format++;
} else if (strchr("p", *format)) {
*type = type_voidpointer;
format++;
} else if (strchr("n", *format)) {
*type = type_intpointer;
format++;
} else {
*type = type_unknown;
exit(1);
}
*type |= mod << CHAR_BIT; // Bring in modifier
return format;
}
void format_next(format_T *state) {
if (state->int_queue > 0) {
state->int_queue--;
return;
}
while (*state->format) {
if (state->format[0] == '%') {
state->format++;
if (state->format[0] == '%') {
state->format++;
continue;
}
state->format = seek_flag(state->format);
state->format = seek_width(state->format, &state->int_queue);
int mod;
state->format = seek_mod(state->format, &mod);
state->format = seek_specifier(state->format, mod, &state->type);
return;
} else {
state->format++;
}
}
state->type = type_none;
}
// 0 Compatible
// 1 Not Compatible
// 2 Not Comparable
int format_cmp(const char *f1, const char *f2) {
format_T state1;
format_init(&state1, f1);
format_T state2;
format_init(&state2, f2);
while (format_get(&state1) == format_get(&state2)) {
if (format_get(&state1) == type_none)
return 0;
if (format_get(&state1) == type_unknown)
return 2;
format_next(&state1);
format_next(&state2);
}
if (format_get(&state1) == type_unknown)
return 2;
if (format_get(&state2) == type_unknown)
return 2;
return 1;
}
注意:只进行了最少的测试。可以添加许多额外的考虑因素。
已知缺点:hh,h,l,ll,j,z,t带有 . 的修饰符n。 l与s,c.
[编辑]
OP 关于安全问题的评论。这改变了职位的性质以及从平等到安全的比较。我想其中一个模式 (A) 将是参考模式,下一个 (B) 将是测试。测试将是“B 至少和 A 一样安全吗?”。示例A = "%.20s"和B1 = "%.19s", B2 = "%.20s", B3 = "%.21s"。 B1并且B2两者都通过了安全测试,因为它们没有提取更多 20 char。 B3是一个问题,因为它通过了 20 的参考限制char。此外,任何非宽度合格的%s %[ %c都是一个安全问题 - 在参考或测试模式中。这个答案的代码没有解决这个问题。
如前所述,代码尚未处理带有"%n".
[2018年编辑]
关于“格式"%d"和"%u"不兼容。”:这是一般要打印的值。对于[0..INT_MAX]范围内的值,任何一种格式都可以按照 C11dr §6.5.2.2 6 工作。