Was ist die Verwendung des% n-Formatbezeichners in C?

125

Was ist die Verwendung des %nFormatbezeichners in C? Könnte jemand mit einem Beispiel erklären?

Josh
quelle
25
Was ist aus der schönen Kunst geworden, das schöne Handbuch zu lesen?
Jens
8
Ich denke, die eigentliche Frage ist, was ist der Punkt einer solchen Option? Warum sollte jemand den Wert der gedruckten Zeichenzahlen wissen wollen und diesen Wert direkt in den Speicher schreiben? Es war, als wären die Entwickler gelangweilt und beschlossen,
jia chen
Deshalb ließ Bionic es los.
Solidak
1
Es ist in der Tat eine gültige Frage, die die feinen Handbücher wahrscheinlich nicht beantworten werden. es wurde entdeckt , dass %nMarken aus printfVersehen Turing-complete und Sie können zB implementieren Brainfuck darin finden github.com/HexHive/printbf und oilshell.org/blog/2019/02/07.html#appendix-a-minor-sublanguages
John Frazer

Antworten:

147

Nichts gedruckt. Das Argument muss ein Zeiger auf ein vorzeichenbehaftetes int sein, in dem die Anzahl der bisher geschriebenen Zeichen gespeichert ist.

#include <stdio.h>

int main()
{
  int val;

  printf("blah %n blah\n", &val);

  printf("val = %d\n", val);

  return 0;

}

Der vorherige Code druckt:

blah  blah
val = 5
Stern-Taste
quelle
1
Sie erwähnen, dass das Argument ein Zeiger auf ein vorzeichenbehaftetes int sein muss, dann haben Sie in Ihrem Beispiel ein vorzeichenloses int verwendet (wahrscheinlich nur ein Tippfehler).
bta
1
@ Andrews: Weil die Funktion den Wert der Variablen ändert.
Jack
3
@ Jack intist immer signiert.
Jamesdlin
1
@ Jamesdlin: Mein Fehler. Es tut mir leid. Ich wusste nicht, wo ich das gelesen habe.
Jack
1
Aus irgendeinem Grund löst das Beispiel einen Fehler mit Hinweis aus n format specifies disabled. Was ist der Grund?
Johnny_D
186

Die meisten dieser Antworten erklären, was zu tun ist ( was %n bedeutet , nichts zu drucken und die Anzahl der bisher gedruckten Zeichen in eine intVariable zu schreiben ), aber bisher hat niemand wirklich ein Beispiel dafür gegeben, welchen Nutzen es hat. Hier ist eine:

int n;
printf("%s: %nFoo\n", "hello", &n);
printf("%*sBar\n", n, "");

wird drucken:

hello: Foo
       Bar

mit Foo und Bar ausgerichtet. (Es ist trivial, dies zu tun, ohne %ndieses spezielle Beispiel zu verwenden, und im Allgemeinen könnte man diesen ersten printfAufruf immer abbrechen :

int n = printf("%s: ", "hello");
printf("Foo\n");
printf("%*sBar\n", n, "");

Ob es sich lohnt, etwas Esoterisches wie die leicht hinzugefügte Bequemlichkeit zu verwenden %n(und möglicherweise Fehler einzuführen), kann diskutiert werden.)

Jamesdlin
quelle
3
Oh mein Gott - dies ist eine zeichenbasierte Version der Berechnung der Pixelgröße eines Strings in einer bestimmten Schriftart!
Können Sie erklären, warum & n und * s benötigt werden? Sind sie beide Zeiger?
Andrew S
9
@AndrewS &nist ein Zeiger ( &ist die Adresse des Operators); Ein Zeiger ist erforderlich, da C ein Wert ist und ohne Zeiger printfden Wert von nicht ändern kann n. Die %*sVerwendung in der printfFormatzeichenfolge druckt einen %sBezeichner (in diesem Fall die leere Zeichenfolge "") unter Verwendung einer Feldbreite von nZeichen. Eine Erklärung der printfGrundprinzipien liegt grundsätzlich außerhalb des Rahmens dieser Frage (und Antwort); Ich würde empfehlen, die printfDokumentation zu lesen oder Ihre eigene Frage zu SO zu stellen.
Jamesdlin
3
Vielen Dank, dass Sie einen Anwendungsfall gezeigt haben. Ich verstehe nicht, warum die Leute das Handbuch einfach kopieren, in SO einfügen und es manchmal umformulieren. Wir sind Menschen und alles geschieht aus einem Grund, der immer in einer Antwort erklärt werden sollte. "Tut nichts" ist wie zu sagen "Das Wort cool bedeutet cool" - fast nutzloses Wissen.
the_endian
1
@PSkocik Es ist kompliziert und fehleranfällig genug, ohne eine zusätzliche Indirektionsebene hinzuzufügen.
Jamesdlin
18

Ich habe nicht wirklich viele praktische Anwendungen des %nSpezifizierers in der realen Welt gesehen , aber ich erinnere mich, dass er vor einiger Zeit bei Druckschwachstellen der alten Schule mit einem Format-String-Angriff verwendet wurde .

Etwas, das so lief

void authorizeUser( char * username, char * password){

    ...code here setting authorized to false...
    printf(username);

    if ( authorized ) {
         giveControl(username);
    }
}

wo ein böswilliger Benutzer die Vorteile des Benutzernamens nehmen könnte Parameter in printf als Format - String übergeben zu werden und eine Kombination aus verwenden %d, %coder w / e durch den Call - Stack zu gehen und dann die Variable ändern , um einen wahren Wert zugelassen.

Ja, es ist eine esoterische Verwendung, aber immer nützlich, wenn Sie einen Daemon schreiben, um Sicherheitslücken zu vermeiden? : D.

Xzhsh
quelle
1
Es gibt mehr Gründe, als %ndie Verwendung einer nicht aktivierten Eingabezeichenfolge als Formatzeichenfolge zu vermeiden printf.
Keith Thompson
13

Von hier aus sehen wir, dass die Anzahl der bisher gedruckten Zeichen gespeichert wird.

n Das Argument soll ein Zeiger auf eine Ganzzahl sein, in die die Anzahl der Bytes geschrieben wird, die bisher durch diesen Aufruf einer der fprintf()Funktionen in die Ausgabe geschrieben wurden . Es wird kein Argument konvertiert.

Eine beispielhafte Verwendung wäre:

int n_chars = 0;
printf("Hello, World%n", &n_chars);

n_charshätte dann einen Wert von 12.

KLee1
quelle
10

Bisher geht es bei allen Antworten darum %n, aber nicht darum, warum irgendjemand es überhaupt wollen würde. Ich finde es etwas nützlich mit sprintf/ snprintf, wenn Sie die resultierende Zeichenfolge später auflösen oder ändern müssen, da der gespeicherte Wert ein Array-Index in der resultierenden Zeichenfolge ist. Diese Anwendung ist jedoch viel nützlicher, sscanfinsbesondere da Funktionen in der scanfFamilie nicht die Anzahl der verarbeiteten Zeichen, sondern die Anzahl der Felder zurückgeben.

Eine andere wirklich hackige Verwendung besteht darin, gleichzeitig ein Pseudo-Log10 kostenlos zu erhalten, während eine Nummer als Teil einer anderen Operation gedruckt wird.

R .. GitHub HÖREN SIE AUF, EIS ZU HELFEN
quelle
+1 für die Erwähnung von Verwendungen für %n, obwohl ich mich über "alle Antworten ..." unterscheiden möchte. = P
Jamesdlin
1
Die bösen Jungs danken Ihnen für Ihre Verwendung von printf /% n, sprintf und sscanf;)
jww
6
@noloader: Wie so? Die Verwendung von %n hat absolut keine Gefahr der Verwundbarkeit für einen Angreifer. Die verlegte Schande von %ngehört wirklich zu der dummen Praxis, eine Nachrichtenzeichenfolge anstelle einer Formatzeichenfolge als Formatargument zu übergeben. Diese Situation tritt natürlich nie auf, wenn %ntatsächlich Teil einer beabsichtigten Formatzeichenfolge verwendet wird.
R .. GitHub STOP HELPING ICE
Mit% n können Sie in den Speicher schreiben. Ich denke, Sie gehen davon aus, dass der Angreifer diesen Zeiger nicht kontrolliert (ich könnte mich irren). Wenn der Angreifer den Zeiger steuert (es ist nur ein weiterer Parameter für printf), kann er einen Schreibvorgang von 4 Bytes ausführen. Ob er / sie profitieren kann, ist eine andere Geschichte.
JWW
8
@noloader: Das gilt für jede Verwendung von Zeigern. Niemand sagt "böse Jungs, danke" fürs Schreiben *p = f();. Warum sollte %n, was nur eine andere Art ist, ein Ergebnis auf das Objekt zu schreiben, auf das ein Zeiger zeigt, als "gefährlich" angesehen werden, anstatt den Zeiger selbst als gefährlich zu betrachten?
R .. GitHub STOP HELPING ICE
10

Das mit dem verknüpfte Argument %nwird als behandelt int*und mit der Anzahl der an diesem Punkt im Feld gedruckten Gesamtzeichen gefüllt printf.

Evan
quelle
9

Neulich befand ich mich in einer Situation, in %nder mein Problem gut gelöst werden konnte. Im Gegensatz zu meiner früheren Antwort kann ich in diesem Fall keine gute Alternative finden.

Ich habe ein GUI-Steuerelement, das einen bestimmten Text anzeigt. Dieses Steuerelement kann einen Teil dieses Textes fett (oder kursiv oder unterstrichen usw.) anzeigen, und ich kann angeben, welcher Teil durch Angabe von Start- und Endzeichenindizes angegeben wird.

In meinem Fall generiere ich den Text für das Steuerelement mit snprintfund möchte, dass eine der Ersetzungen fett gedruckt wird. Das Finden der Start- und Endindizes für diese Substitution ist nicht trivial, weil:

  • Die Zeichenfolge enthält mehrere Ersetzungen, und eine der Ersetzungen ist beliebiger, benutzerdefinierter Text. Dies bedeutet, dass eine Textsuche nach der Substitution, die mir wichtig ist, möglicherweise nicht eindeutig ist.

  • Die Formatzeichenfolge ist möglicherweise lokalisiert und verwendet möglicherweise die $POSIX-Erweiterung für Positionsformatspezifizierer. Daher ist das Durchsuchen der ursprünglichen Formatzeichenfolge nach den Formatspezifizierern selbst nicht trivial.

  • Der Lokalisierungsaspekt bedeutet auch, dass ich die Formatzeichenfolge nicht einfach in mehrere Aufrufe an aufteilen kann snprintf.

Daher ist es am einfachsten, die Indizes für eine bestimmte Substitution zu finden:

char buf[256];
int start;
int end;

snprintf(buf, sizeof buf,
         "blah blah %s %f yada yada %n%s%n yakety yak",
         someUserSpecifiedString,
         someFloat,
         &start, boldString, &end);
control->set_text(buf);
control->set_bold(start, end);
Jamesdlin
quelle
Ich gebe Ihnen +1 für den Anwendungsfall. Sie werden jedoch ein Audit nicht bestehen. Daher sollten Sie wahrscheinlich einen anderen Weg finden, um den Anfang und das Ende des fett gedruckten Textes zu markieren. Es scheint, als würden drei snprintfbeim Überprüfen der Rückgabewerte einwandfrei funktionieren, da snprintfdie Anzahl der geschriebenen Zeichen zurückgegeben wird. Vielleicht so etwas wie: int begin = snprintf(..., "blah blah %s %f yada yada", ...);und int end = snprintf(..., "%s", ...);dann der Schwanz: snprintf(..., "blah blah");.
JWW
3
@jww Das Problem bei mehreren snprintfAufrufen besteht darin, dass die Ersetzungen möglicherweise in anderen Gebietsschemas neu angeordnet werden, sodass sie nicht so aufgeteilt werden können.
Jamesdlin
Danke für das Beispiel. Aber könnten Sie nicht eine Terminalsteuerungssequenz schreiben, um die Ausgabe direkt vor dem Feld fett zu machen, und dann eine Sequenz danach schreiben? Wenn Sie die Terminalsteuerungssequenzen nicht fest codieren, können Sie sie auch positionell (nachbestellbar) machen.
PSkocik
1
@PSkocik Wenn Sie an ein Terminal ausgeben. Wenn Sie beispielsweise mit einem Win32-Rich-Edit-Steuerelement arbeiten, hilft dies nur, wenn Sie die Terminalsteuerungssequenzen anschließend erneut analysieren möchten. Dies setzt auch voraus, dass Sie Terminalsteuerungssequenzen im Rest des ersetzten Textes berücksichtigen möchten. Wenn Sie dies nicht tun, müssten Sie diese filtern oder entkommen. Ich sage nicht, dass es unmöglich ist, darauf zu verzichten %n. Ich behaupte, dass die Verwendung %neinfacher ist als Alternativen.
Jamesdlin
1

Es wird nichts gedruckt. Es wird verwendet, um herauszufinden, wie viele Zeichen gedruckt wurden, bevor %nsie in der Formatzeichenfolge angezeigt wurden, und um diese an das angegebene int auszugeben:

#include <stdio.h>

int main(int argc, char* argv[])
{
    int resultOfNSpecifier = 0;
    _set_printf_count_output(1); /* Required in visual studio */
    printf("Some format string%n\n", &resultOfNSpecifier);
    printf("Count of chars before the %%n: %d\n", resultOfNSpecifier);
    return 0;
}

( Dokumentation für_set_printf_count_output )

Merlyn Morgan-Graham
quelle
0

Darin wird der Wert der Anzahl der bisher gedruckten Zeichen gespeichert printf() Funktion .

Beispiel:

int a;
printf("Hello World %n \n", &a);
printf("Characters printed so far = %d",a);

Die Ausgabe dieses Programms wird sein

Hello World
Characters printed so far = 12
Sudhanshu Mishra
quelle
Wenn ich versuche, Sie zu codieren, gibt es mir: Hallo Weltzeichen, die bisher gedruckt wurden = 36 ,,,,, warum 36 ?! Ich verwende ein 32-Bit-GCC in einem Windows-Computer.
Sina Karvandi
-6

% n ist C99, funktioniert nicht mit VC ++.

user411313
quelle
2
%nexistierte in C89. Es funktioniert nicht mit MSVC, da Microsoft es aus Sicherheitsgründen standardmäßig deaktiviert hat. Sie müssen _set_printf_count_outputzuerst aufrufen , um es zu aktivieren. (Siehe die Antwort von Merlyn Morgan-Graham.)
Jamesdlin
Nein, C89 definiert diese Funktion / diesen Backdoorbug nicht. Siehe K & R + ANSI-C amazon.com/Programming-Language-2nd-Brian-Kernighan/dp/… ?? Wo ist der URL-Tagger für Kommentare?
user411313
4
Du liegst einfach falsch. Es ist in Tabelle B-1 ( printfUmrechnungen) von Anhang B von K & R, 2. Auflage, klar aufgeführt . (Seite 244 meiner Kopie.) Oder siehe Abschnitt 7.9.6.1 (Seite 134) der ISO C90-Spezifikation.
Jamesdlin
Android hat auch den% n-Bezeichner entfernt.
JWW