Beispiele:
"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
Ich dachte, es sollte dafür eine C-Funktion geben, aber ich bekomme keine relevanten Suchergebnisse. Ich meine, der Compiler überprüft, ob die Formatzeichenfolge und die Argumente übereinstimmen, also ist der Code für die Überprüfung bereits geschrieben. Die Frage ist nur, wie ich es nennen kann.
Ich verwende Objective-C, wenn es also eine Objective-C-spezifische Lösung gibt, ist das auch in Ordnung.
prüfen, ob 2 printf()
Formatstrings sind kompatibel ist eine Übung zum Analysieren von Formaten.
Zumindest C hat keine Standard-Laufzeit-Vergleichsfunktion wie:
int format_cmp(const char *f1, const char *f2); // Does not exist
Formate wie "%d %f"
und "%i %e"
sind offensichtlich insofern kompatibel, als beide ein erwarten int
und float/double
. Notiz: float
dazu befördert werden double
wie short
und signed char
dazu befördert werden int
.
Formate "%*.*f"
und "%i %d %e"
sind kompatibel, aber nicht offensichtlich: beide erwarten eine int
,int
und float/double
.
Formate "%hhd"
und "%d"
beide erwarten ein int
auch wenn die ersten Werte in umgewandelt werden signed char
vor dem Drucken.
Formate "%d"
und "%u"
sind nicht kompatibel. Auch wenn sich viele Systeme wie erhofft verhalten werden. Hinweis: Normalerweise char
wird fördern int
.
Formate "%d"
und "%ld"
sind nicht streng kompatibel. Auf einem 32-Bit-System gibt es Äquivalente, aber nicht im Allgemeinen. Natürlich kann der Code geändert werden, um dies zu berücksichtigen. OTOH "%lf"
und "%f"
sind kompatibel aufgrund der üblichen Argumentationsförderungen von float
zu double
.
Formate "%lu"
und "%zu"
kann kompatibel sein, aber das hängt von der Implementierung ab unsigned long
und size_t
. Ergänzungen zum Code könnten dies oder verwandte Äquivalenzen zulassen.
Einige Kombinationen von Modifikatoren und Spezifizierern sind nicht wie definiert "%zp"
. Das Folgende schließt solche esoterischen Kombinationen nicht aus – vergleicht sie aber.
Modifikatoren wie "$"
sind Erweiterungen zu Standard C und werden im Folgenden nicht implementiert.
Der Kompatibilitätstest für printf()
unterscheidet sich von 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;
}
Hinweis: Nur minimale Tests durchgeführt. Viele zusätzliche Überlegungen könnten hinzugefügt werden.
Bekannte Mängel: hh,h,l,ll,j,z,t
Modifikatoren mit n
. l
mit s,c
.
[Edit]
OP-Kommentare zu Sicherheitsbedenken. Dies ändert die Art des Posts und des Vergleichs von einem Gleichheitsposten zu einem Sicherheitsposten. Ich würde mir vorstellen, dass eines der Muster (A) ein Referenzmuster und das nächste (B) der Test wäre. Der Test wäre “Ist B mindestens so sicher wie A?”. Beispiel A = "%.20s"
und B1 = "%.19s"
, B2 = "%.20s"
, B3 = "%.21s"
. B1
und B2
beide bestehen den Sicherheitstest, da sie nicht mehr als 20 extrahieren char
. B3
ist ein Problem, da es die Referenzgrenze von 20 überschreitet char
. Des Weiteren irgendein nicht Breite qualifiziert mit %s %[ %c
is a security problem – in the reference or test pattern. This answer’s code does not address this issue.
As mentioned, code does not yet handle modifiers with "%n"
.
[2018 edit]
Zum Thema „Formate "%d"
und "%u"
sind nicht kompatibel.”: Dies gilt allgemein für zu druckende Werte. Für Werte in der [0..INT_MAX]
Reichweite, jedes Format kann gemäß C11dr §6.5.2.2 funktionieren 6.
Mein Verständnis von dem, was Sie wollen, ist, dass Sie im Grunde eine Methode wollen, die zwei Strings betrachten und erkennen kann, ob sie beide die gleichen Arten von Werten enthalten. Oder etwas in dieser Länge … Wenn ja, dann versuchen Sie dies (oder etwas in der Art):
-(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;
}
}
Du kannst dich vielleicht bücken
NS_FORMAT_FUNCTION
zu deinem Willen. Überprüfen Sie diese SO-Antwort sowie die Clang-Dokumente für__format__
.– Ravron
9. März 2015 um 16:58 Uhr
Oder suchen Sie hier nach GNU: gnu.org/software/libc/manual/html_node/…
– Eugen Sch.
9. März 2015 um 17:00 Uhr
Ich denke, das Problem ist, dass die von Apple bereitgestellte libc nicht glibc ist, aber vielleicht haben sie etwas Ähnliches …
– Erich B
9. März 2015 um 17:42 Uhr
@ErikB: Wenn Sie funktionierenden Code wünschen, kontaktieren Sie mich (siehe mein Profil). Ich habe ungefähr 240 C-Zeilen, die sich speziell auf den Vergleich von Formatzeichenfolgen beziehen (plus mehrere hundert Zeilen Testcode/Daten), und dann 500 Zeilen (bereits vorhandenen) Formatanalysecode plus 300 weitere Zeilen Testcode/Daten , plus den Testunterstützungsgurt (ebenfalls bereits vorhanden). Wie gesagt, alles summiert sich zu viel mehr Code, als vernünftig in eine Antwort passt.
– Jonathan Leffler
23. März 2015 um 4:21 Uhr
Das
$
in"Something %d and %f" and "Something %2$f and %1$d"
ist nicht Bestandteil des C-Standards. Dies sollte zu einer 3. Antwort führen: „nicht vergleichbar“.– chux – Wiedereinsetzung von Monica
8. April 2015 um 17:29 Uhr