如何检查两个格式字符串是否兼容?

时间:2015-03-09 16:51:35

标签: objective-c c printf compatibility format-string

示例:

"Something %d"        and "Something else %d"       // Compatible
"Something %d"        and "Something else %f"       // Not Compatible
"Something %d"        and "Something %d else %d"    // Not Compatible
"Something %d and %f" and "Something %2$f and %1$d" // Compatible

我认为应该有一些C功能,但我没有得到任何相关的搜索结果。我的意思是编译器正在检查格式字符串和参数是否匹配,因此已经编写了用于检查它的代码。唯一的问题是我怎么称呼它。

我正在使用Objective-C,所以如果有一个特定于Objective-C的解决方案也没关系。

2 个答案:

答案 0 :(得分:9)

检查2 printf()格式字符串是否兼容是格式解析的练习。

C,至少没有标准的运行时比较功能,例如:

int format_cmp(const char *f1, const char *f2); // Does not exist

"%d %f""%i %e"等格式显然兼容,因为它们都期望intfloat/double。注意:float被提升为double shortsigned char被提升为int

格式"%*.*f""%i %d %e"是兼容的,但并不明显:两者都期望intintfloat/double

格式"%hhd""%d"都期望int,即使第一个会在打印前将其值转换为signed char

格式"%d""%u" 兼容。即使许多系统都表现得像希望的那样。注意:通常char会提升为int

格式"%d""%ld" 严格兼容。在32位系统上有相同的,但不是一般的。当然可以改变代码以适应这种情况。由于"%lf"通常会将"%f"提升为float,因此OTOH double"%lu" 兼容。

格式"zu"unsigned long 可能兼容,但这取决于size_t"zp"的实施情况。对代码的添加可以允许这种或相关的等价。

修饰符和说明符的某些组合未定义为"$"。以下不会禁止这种深奥的组合 - 但会比较它们。

printf()等修饰符是标准C的扩展,未在下面实现。

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修饰符ls,cA = "%.20s"

[编辑]

OP关于安全问题的评论。这改变了帖子的性质以及从平等和安全性的比较。我想象其中一个模式(A)将是一个参考模式,下一个(B)将是测试。测试将是“B至少和A一样安全吗?”。示例B1 = "%.19s"B2 = "%.20s"B3 = "%.21s"B1B2char都通过了安全测试,因为它们不会提取更多的20 B3char是一个问题,因为它超过了参考限制20 %s %[ %c。进一步任何非宽度限定"%n"是一个安全问题 - 在参考或测试模式中。这个答案的代码没有解决这个问题。

如前所述,代码尚未处理"%d"的修饰符。

[2018编辑]

关于“格式"%u"[0..INT_MAX]不兼容。”:这适用于一般打印的值。对于{{1}}范围内的值,根据C11dr§6.5.2.26,任一格式都可以工作。

答案 1 :(得分:-1)

我对你想要的东西的理解是,你基本上想要一个方法,它可以查看两个字符串并检测它们是否都具有相同类型的值。或者那些长长的东西......如果是这样的话,那么试试这个(或类似的东西):

-(int)checkCompatible:(NSString *)string_1 :(NSString *)string_2 {

    // Separate the string into single elements.
    NSArray *stringArray_1 = [string_1 componentsSeparatedByString:@" "];
    NSArray *stringArray_2 = [string_2 componentsSeparatedByString:@" "];

    // Store only the numbers for comparison in a new array.
    NSMutableArray *numbers_1 = [[NSMutableArray alloc] init];
    NSMutableArray *numbers_2 = [[NSMutableArray alloc] init];

    // Make sure the for loop below, runs for the appropriate
    // number of cycles depending on which array is bigger.
    int loopMax = 0;

    if ([stringArray_1 count] > [stringArray_2 count]) {
        loopMax = (int)[stringArray_1 count];
    } 

    else {
        loopMax = (int)[stringArray_2 count];
    }

    // Now go through the stringArray's and store only the 
    // numbers in the mutable array's. This will be used 
    // during the comparison stage.
    for (int loop = 0; loop < loopMax; loop++) {

        NSCharacterSet *notDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];

        if (loop < [stringArray_1 count]) {

            if ([[stringArray_1 objectAtindex:loop] rangeOfCharacterFromSet:notDigits].location == NSNotFound) {
                // String consists only of the digits 0 through 9.
                [numbers_1 addObject:[stringArray_1 objectAtindex:loop]];
            }
        }

        if (loop < [stringArray_2 count]) {

            if ([[stringArray_2 objectAtindex:loop] rangeOfCharacterFromSet:notDigits].location == NSNotFound) {
                // String consists only of the digits 0 through 9.
                [numbers_2 addObject:[stringArray_2 objectAtindex:loop]];
            }
        }
    }

    // Now look through the mutable array's
    // and perform the type comparison,.

    if ([numbers_1 count] != [numbers_2 count]) {

        // One of the two strings has more numbers 
        // than the other, so they are NOT compatible.
        return 1;
    }

    else {

        // Both string have the same number of  numbers
        // numbers so lets go through them to make 
        // sure the  numbers are of the same type.
        for (int loop = 0; loop < [numbers_1 count]; loop++) {

            // Check to see if the number in the current array index
            // is a float or an integer. All the numbers in the array have
            // to be the SAME type, in order for the strings to be compatible.
            BOOL check_float_1 = [[NSScanner scannerWithString:[numbers_1 objectAtindex:loop]] scanFloat:nil];
            BOOL check_int_1 = [[NSScanner scannerWithString:[numbers_1 objectAtindex:loop]] scanInt:nil];
            BOOL check_float_2 = [[NSScanner scannerWithString:[numbers_2 objectAtindex:loop]] scanFloat:nil];
            BOOL check_int_2 = [[NSScanner scannerWithString:[numbers_2 objectAtindex:loop]] scanInt:nil];

            if (check_float_1 == YES) {

                if (check_float_2 == NO) {
                    return 1;
                }
            }

            else if (check_int_1 == YES) {

                if (check_int_2 == NO) {
                    return 1;
                }
            }

            else {
                // Error of some sort......
                return 1;
            }
        }

        // All the numbers in the strings are of the same
        // type (otherwise we would NOT have reached
        // this point). Therefore the strings are compatible.
        return 0;
      }
}