Wie überprüfe ich, ob zwei Formatzeichenfolgen kompatibel sind?

Lesezeit: 10 Minuten

Benutzeravatar von Erik B
Erik B

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.

  • 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

chux – Stellt Monicas Benutzeravatar wieder her
Chux – Wiedereinsetzung von Monica

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 intauch 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.

  • Ich mag diese Antwort sehr. Wenn Op damit einverstanden ist, hast du dieses Kopfgeld verdient.

    – Mekap

    13. Mai 2015 um 8:06 Uhr

  • Ich hatte keine Zeit, den Code zu lesen, auszuführen oder zu testen, aber es scheint, als hätten Sie die Frage verstanden. Mein Hauptanliegen ist die Sicherheit. Ich fand heraus, dass wir Formatzeichenfolgen aus einer Konfigurationsdatei erhielten, die aus dem Internet heruntergeladen werden konnte. Das bedeutet, dass ein Angreifer einen Formatstring einfügen könnte. Ich habe das mit einer für unseren Fall spezifischen Implementierung behoben, aber ich dachte, es sollte eine allgemeine Implementierung zum Überprüfen der Kompatibilität von Formatzeichenfolgen geben. Obwohl Ihre Implementierung gut funktionieren könnte, fühle ich mich nicht wohl dabei, sie zu verwenden, da ich etwas gut getestetes und kampferprobtes haben möchte.

    – Erich B

    13. Mai 2015 um 10:08 Uhr

  • @Erik B Die Sicherheitsbedenken, IMO, ändern den Fokus der Frage erheblich. Ich habe meiner Antwort einige hinzugefügt, um dieses Problem anzugehen, aber Sicherheit ist wirklich eine neue Frage. Fügen Sie vielleicht einen neuen Beitrag mit Details zu Ihren Sicherheitsbedenken hinzu – insbesondere in den Bereichen Stiche und "%n". IAC, ich glaube nicht, dass Sie eine fertige Implementierung finden werden.

    – chux – Wiedereinsetzung von Monica

    13. Mai 2015 um 15:43 Uhr


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

  • Das erste Problem besteht darin, die Zeichenfolgen durch Leerzeichen aufzuteilen. Wieso den?

    – Sulthan

    7. Mai 2015 um 9:42 Uhr


  • @Sulthan Ich teile sie nicht durch Leerzeichen. Was ich dort mache, ist, eine Kopie aller einzelnen Zeichenfolgen in dieser Zeichenfolge zu erhalten und sie in separaten Array-Elementen zu speichern. Nehmen wir also an, die Zeichenfolge war “Hallo 123”, dann danach componentsSeparatedByStringwürde es im Array als “Hallo” gespeichert werden [0] und “123” [1].

    Benutzer4657588

    7. Mai 2015 um 9:45 Uhr

  • Wie wäre es mit “Etwas %d” und “Etwas %d”? Ich denke, sie werden aufgrund der Frage als “kompatibel” angesehen.

    – Yuchen

    11. Mai 2015 um 1:00 Uhr

  • Dies beantwortet meine Frage überhaupt nicht. “Hallo 123” ist kein Formatstring. Ich glaube du hast die Frage nicht verstanden.

    – Erich B

    12. Mai 2015 um 16:11 Uhr

  • @ErikB Oh, Entschuldigung. Vielleicht könntest du deine Frage für uns Einfaltspinsel besser erklären.

    Benutzer4657588

    13. Mai 2015 um 7:20 Uhr

1394620cookie-checkWie überprüfe ich, ob zwei Formatzeichenfolgen kompatibel sind?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy