Warum müssen wir printf () den Datentyp in C mitteilen?

8

Betrachten wir diesen C-Code:

#include <stdio.h>

main()
{
  int x=5;
  printf("x is ");
  printf("%d",5);
}

Als wir das geschrieben haben, haben int x=5;wir dem Computer gesagt, dass xes sich um eine Ganzzahl handelt. Der Computer muss sich daran erinnern, dass xes sich um eine Ganzzahl handelt. Wenn wir jedoch den Wert von xin ausgeben, müssen printf()wir dem Computer erneut mitteilen, dass xes sich um eine Ganzzahl handelt. Warum ist das so?

Warum vergisst der Computer, dass xes sich um eine Ganzzahl handelt?

user106313
quelle
5
Der Compiler weiß, dass x eine Ganzzahl ist, printf jedoch nicht.
luiscubal
Es ist nur so, wie die Aufrufkonvention funktioniert, es gibt nur eine Implementierung von printf(char*, ...)und es wird nur ein Zeiger auf eine Sammlung von Daten
angezeigt
@ratchetfreak Warum ist es so? Warum erhält printf den Zeiger des Datentyps nicht automatisch wie in C ++, wo die Funktion "cout" den Datentyp automatisch kennt?
user106313
5
Hast du darüber nachgedacht printf("x is %x in hex, and %d in decimal and %o as octal",x,x,x);?
1
Versuchen Sie es mit der Nummer 163. Werte unter 8 sind in dieser Situation eher uninteressant. Oder noch besser, schleifen Sie von 0 bis 255 und sehen Sie, wie die Zahlen lauten. Der Punkt ist, dass printf mehr enthält als nur sie eingeben.

Antworten:

18

Hier spielen zwei Probleme eine Rolle:

Problem Nr. 1: C ist eine statisch typisierte Sprache. Alle Typinformationen werden zur Kompilierungszeit ermittelt. Mit keinem Objekt im Speicher werden Typinformationen gespeichert, sodass Typ und Größe zur Laufzeit 1 bestimmt werden können . Wenn Sie den Speicher an einer bestimmten Adresse untersuchen, während das Programm ausgeführt wird, sehen Sie nur einen Schlamm von Bytes. Es gibt nichts zu sagen, ob diese bestimmte Adresse tatsächlich ein Objekt enthält, welchen Typ oder welche Größe dieses Objekt hat oder wie diese Bytes zu interpretieren sind (als Ganzzahl- oder Gleitkommatyp oder als Zeichenfolge in einer Zeichenfolge usw.). ). Alle diese Informationen werden beim Kompilieren des Codes in den Maschinencode eingebrannt, basierend auf den im Quellcode angegebenen Typinformationen. Zum Beispiel die Funktionsdefinition

void foo( int x, double y, char *z )
{
  ...
}

Weist den Compiler an, den entsprechenden Maschinencode zu generieren, der xals Ganzzahl, yals Gleitkommawert und zals Zeiger auf behandelt werden soll char. Beachten Sie, dass Fehlanpassungen in der Anzahl oder Art der Argumente zwischen einem Funktionsaufruf und einer Funktionsdefinition nur erkannt werden, wenn der Code kompiliert wird 2 ; Nur während der Kompilierungsphase werden einem Objekt Typinformationen zugeordnet.

Problem Nr. 2: printfist eine variable Funktion; Es werden ein fester Parameter vom Typ const char * restrict(die Formatzeichenfolge) sowie null oder mehr zusätzliche Parameter verwendet, deren Anzahl und Typ zum Zeitpunkt der Kompilierung nicht bekannt sind :

int printf( const char * restrict fmt, ... );

Die printfFunktion kann die Anzahl und Art der zusätzlichen Argumente nicht anhand der übergebenen Argumente selbst ermitteln. Es muss sich auf die Formatzeichenfolge verlassen, um zu bestimmen, wie der Byte-Schlamm auf dem Stapel (oder in den Registern) interpretiert werden soll. Noch besser ist , weil es eine variadische Funktion ist, Argumente mit bestimmten Typen werden gefördert , um eine begrenzte Anzahl von Standardtypen (zB shortwird gefördert int, floatwird gefördert double, etc.).

Auch hier sind den zusätzlichen Argumenten selbst keine Informationen zugeordnet, die printfHinweise auf deren Interpretation oder Formatierung geben könnten. Daher sind die Konvertierungsspezifizierer in der Formatzeichenfolge erforderlich.

Beachten Sie, dass printfKonvertierungsspezifizierer nicht nur die Anzahl und den Typ zusätzlicher Argumente angeben, sondern auch angeben, printfwie die Ausgabe formatiert werden soll (Feldbreiten, Genauigkeit, Auffüllung, Ausrichtung, Basis (Dezimal, Oktal oder Hex für Ganzzahltypen) usw.).

Bearbeiten

Um ausführliche Diskussionen in den Kommentaren zu vermeiden (und weil die Chat-Seite für mein Arbeitssystem gesperrt ist - ja, ich bin ein böser Junge), werde ich hier die letzten beiden Fragen beantworten.

WENN ich das mache:
float b;          
float c;           
b=3.1;    
c=(5.0/9.0)*(b);
Woher weiß der Compiler in der letzten Anweisung, dass b vom Typ float ist?

Während der Übersetzung hält der Compiler eine Tabelle (oft genannt Symboltabelle ) , dass speichert Informationen über den Namen eines Objekts, Typ, Lagerdauer, Umfang, etc. Sie erklärt b und cwie float, so jederzeit der Compiler sieht einen Ausdruck mit boder cdarin, Es wird der Maschinencode generiert, um einen Gleitkommawert zu verarbeiten.

Ich habe Ihren Code oben genommen und ein vollständiges Programm darum gewickelt:

/**
 * c1.c
 */
#include <stdio.h>
int main( void )
{
  float b;
  float c;
  b = 3.1;
  c = (5.0 / 9.0) * b;

  printf( "c = %f\n", c );
  return 0;
}

Ich habe die Optionen -gund -Wa,-aldhmit gcc verwendet, um eine Liste des generierten Maschinencodes zu erstellen, der mit dem C-Quellcode 3 verschachtelt ist :

GAS LISTING /tmp/ccmGgGG2.s                     page 1

   1                            .file   "c1.c"
   9                    .Ltext0:
  10                            .section        .rodata
  11                    .LC2:
  12 0000 63203D20              .string "c = %f\n"
  12      25660A00
  13                            .align 8
  14                    .LC1:
  15 0008 721CC771              .long   1908874354
  16 000c 1CC7E13F              .long   1071761180
  17                            .text
  18                    .globl main
  20                    main:
  21                    .LFB2:
  22                            .file 1 "c1.c"
   1:c1.c          **** #include <stdio.h>
   2:c1.c          **** int main( void )
   3:c1.c          **** {
  23                            .loc 1 3 0
  24 0000 55                    pushq   %rbp
  25                    .LCFI0:
  26 0001 4889E5                movq    %rsp, %rbp
  27                    .LCFI1:
  28 0004 4883EC10              subq    $16, %rsp
  29                    .LCFI2:
   4:c1.c          ****   float b;
   5:c1.c          ****   float c;
   6:c1.c          ****   b = 3.1;
  30                            .loc 1 6 0
  31 0008 B8666646              movl    $0x40466666, %eax
  31      40
  32 000d 8945F8                movl    %eax, -8(%rbp)
   7:c1.c          ****   c = (5.0 / 9.0) * b;
  33                            .loc 1 7 0
  34 0010 F30F5A4D              cvtss2sd        -8(%rbp), %xmm1
  34      F8
  35 0015 F20F1005              movsd   .LC1(%rip), %xmm0
  35      00000000
  36 001d F20F59C1              mulsd   %xmm1, %xmm0
  37 0021 F20F5AC0              cvtsd2ss        %xmm0, %xmm0
  38 0025 F30F1145              movss   %xmm0, -4(%rbp)
  38      FC
   8:c1.c          ****
   9:c1.c          ****   printf( "c = %f\n", c );
  39                            .loc 1 9 0
  40 002a F30F5A45              cvtss2sd        -4(%rbp), %xmm0
  40      FC
  41 002f BF000000              movl    $.LC2, %edi
  41      00
  42 0034 B8010000              movl    $1, %eax
  42      00
  43 0039 E8000000              call    printf
  43      00
  10:c1.c          ****   return 0;
  44                            .loc 1 10 0
  45 003e B8000000              movl    $0, %eax

GAS LISTING /tmp/ccmGgGG2.s                     page 2

  11:c1.c          **** }
  46                            .loc 1 11 0
  47 0043 C9                    leave
  48 0044 C3                    ret

So lesen Sie die Baugruppenliste:

  40 002a F30F5A45              cvtss2sd        -4(%rbp), %xmm0
  40      FC
  ^  ^    ^                     ^               ^
  |  |    |                     |               |
  |  |    |                     |               +-- Instruction operands
  |  |    |                     +------------------ Instruction mnemonic
  |  |    +---------------------------------------- Actual machine code (instruction and operands)
  |  +--------------------------------------------- Byte offset of instruction from subroutine entry point
  +------------------------------------------------ Line number of assembly listing

Eine Sache, die hier zu beachten ist. Im generierten Assemblycode gibt es keine Symbole für boder c; Sie sind nur in der Quellcodeliste vorhanden. Bei der mainAusführung zur Laufzeit wird durch Anpassen des Stapelzeigers Platz für bund c(zusammen mit einigen anderen Dingen) vom Stapel zugewiesen:

subq    $16, %rsp

Der Code bezieht sich auf diese Objekte durch ihren Versatz vom Rahmenzeiger 4 , bwobei -8 Bytes von der im Rahmenzeiger gespeicherten Adresse und c-4 Bytes von dieser wie folgt sind:

   7:c1.c          ****   c = (5.0 / 9.0) * b;
  .loc 1 7 0
  cvtss2sd        -8(%rbp), %xmm1  ;; converts contents of b from single- to double-
                                   ;; precision float, stores result to floating-
                                   ;; point register xmm1
  movsd   .LC1(%rip), %xmm0        ;; writes the pre-computed value of 5.0/9.0  
                                   ;; to floating point register xmm0
  mulsd   %xmm1, %xmm0             ;; multiply contents of xmm1 by xmm0, store result
                                   ;; in xmm0
  cvtsd2ss        %xmm0, %xmm0     ;; convert result in xmm0 from double- to single-
                                   ;; precision float
  movss   %xmm0, -4(%rbp)          ;; save result to c

Da Sie deklariert bund cals Gleitkommazahlen angegeben haben, hat der Compiler Maschinencode generiert, um Gleitkommawerte spezifisch zu verarbeiten. die movsd, mulsd, cvtss2sdAnweisungen sind spezifisch für Gleitkommaoperationen, und die Register %xmm0und %xmm1werden verwendet , mit doppelter Genauigkeit zum Speichern von Gleitkommazahlen.

Wenn ich den Quellcode ändern , so dass bund cganze Zahlen anstelle von Schwimmern, erzeugt der Compiler verschiedenen Maschinencode:

/**
 * c2.c
 */
#include <stdio.h>
int main( void )
{
  int b;
  int c;
  b = 3;
  c = (9 / 4) * b; // changed these values since integer 5/9 == 0, making for
                   // some really boring machine code.

  printf( "c = %d\n", c );
  return 0;
}

Kompilieren mit gcc -o c2 -g -std=c99 -pedantic -Wall -Werror -Wa,-aldh=c2.lst c2.cergibt:

GAS LISTING /tmp/ccyxHwid.s                     page 1

   1                            .file   "c2.c"
   9                    .Ltext0:
  10                            .section        .rodata
  11                    .LC0:
  12 0000 63203D20              .string "c = %d\n"
  12      25640A00
  13                            .text
  14                    .globl main
  16                    main:
  17                    .LFB2:
  18                            .file 1 "c2.c"
   1:c2.c          **** #include <stdio.h>
   2:c2.c          **** int main( void )
   3:c2.c          **** {
  19                            .loc 1 3 0
  20 0000 55                    pushq   %rbp
  21                    .LCFI0:
  22 0001 4889E5                movq    %rsp, %rbp
  23                    .LCFI1:
  24 0004 4883EC10              subq    $16, %rsp
  25                    .LCFI2:
   4:c2.c          ****   int b;
   5:c2.c          ****   int c;
   6:c2.c          ****   b = 3;
  26                            .loc 1 6 0
  27 0008 C745F803              movl    $3, -8(%rbp)
  27      000000
   7:c2.c          ****   c = (9 / 4) * b;
  28                            .loc 1 7 0
  29 000f 8B45F8                movl    -8(%rbp), %eax
  30 0012 01C0                  addl    %eax, %eax
  31 0014 8945FC                movl    %eax, -4(%rbp)
   8:c2.c          ****
   9:c2.c          ****   printf( "c = %d\n", c );
  32                            .loc 1 9 0
  33 0017 8B75FC                movl    -4(%rbp), %esi
  34 001a BF000000              movl    $.LC0, %edi
  34      00
  35 001f B8000000              movl    $0, %eax
  35      00
  36 0024 E8000000              call    printf
  36      00
  10:c2.c          ****   return 0;
  37                            .loc 1 10 0
  38 0029 B8000000              movl    $0, %eax
  38      00
  11:c2.c          **** }
  39                            .loc 1 11 0
  40 002e C9                    leave
  41 002f C3                    ret

Hier ist dieselbe Operation, jedoch mit bund cals Ganzzahlen deklariert:

   7:c2.c          ****   c = (9 / 4) * b;
  .loc 1 7 0
  movl    -8(%rbp), %eax  ;; copy value of b to register eax
  addl    %eax, %eax      ;; since 9/4 == 2 (integer arithmetic), double the
                          ;; value in eax
  movl    %eax, -4(%rbp)  ;; write result to c

Dies habe ich vorhin gemeint, als ich sagte, dass Typinformationen in den Maschinencode "eingebrannt" wurden. Wenn das Programm ausgeführt wird, wird der Typ nicht überprüft boder cermittelt. Es weiß bereits, welcher Typ auf dem generierten Maschinencode basieren soll .

Wenn der Compiler zur Laufzeit den Typ und die Größe bestimmt, warum funktioniert dann das folgende Programm nicht:
float b='H';         
printf(" value of b is %c \n",b);

Es funktioniert nicht, weil Sie den Compiler anlügen. Sie sagen, dass dies a bist float, sodass Maschinencode für die Verarbeitung von Gleitkommawerten generiert wird. Wenn Sie es initialisieren, wird das der Konstante entsprechende Bitmuster 'H'als Gleitkommawert und nicht als Zeichenwert interpretiert.

Sie belügen den Compiler erneut, wenn Sie für das Argument den %cKonvertierungsspezifizierer verwenden, der einen Wert vom Typ erwartet . Aus diesem Grund wird der Inhalt von nicht richtig interpretiert , und Sie erhalten die Müllausgabe 5 . Auch hier kann die Anzahl oder Art der zusätzlichen Argumente nicht anhand der Argumente selbst ermittelt werden. Alles, was es sieht, ist eine Adresse auf dem Stapel (oder eine Reihe von Registern). Die Formatzeichenfolge muss angeben, welche zusätzlichen Argumente übergeben wurden und welche Typen sie haben. charbprintfbprintf


1. Die einzige Ausnahme sind Arrays mit variabler Länge. Da ihre Größe erst zur Laufzeit festgelegt wird, gibt es keine Möglichkeit, sizeofeine VLA zur Kompilierungszeit auszuwerten .

2. Ab C89 jedenfalls. Zuvor konnte der Compiler nur Fehlanpassungen im Funktionsrückgabetyp abfangen. Es konnten keine Fehlanpassungen in den Funktionsparameterlisten festgestellt werden.

3. Dieser Code wird auf einem 64-Bit-SuSE Linux Enterprise 10-System mit gcc 4.1.2 generiert. Wenn Sie sich in einer anderen Implementierung befinden (Compiler / Betriebssystem / Chip-Architektur), sind die genauen Maschinenanweisungen unterschiedlich, aber der allgemeine Punkt bleibt bestehen. Der Compiler generiert verschiedene Anweisungen zum Behandeln von Floats vs. Ints vs. Strings usw.

4. Wenn Sie eine Funktion in einem laufenden Programm aufrufen, einen Stack-Framewird erstellt, um die Funktionsargumente, lokalen Variablen und die Adresse der Anweisung nach dem Funktionsaufruf zu speichern. Ein spezielles Register, das als Rahmenzeiger bezeichnet wird, wird verwendet, um den aktuellen Rahmen zu verfolgen.

5. Nehmen Sie beispielsweise ein Big-Endian-System an, bei dem das höherwertige Byte das adressierte Byte ist. Das Bitmuster für Hwird gespeichert werden bals 0x00000048. Da der %cKonvertierungsspezifizierer jedoch angibt, dass das Argument a sein soll char, wird nur das erste Byte gelesen, und es printfwird versucht, das der Codierung entsprechende Zeichen zu schreiben 0x00.

John Bode
quelle
Wenn C eine statisch typisierte Sprache ist, wie druckt putchar () dann den richtigen Datentyp, ohne den Typ zu erwähnen?
user106313
@ user31782: Die Definition der putcharFunktion besagt, dass 1 Argument vom Typ erwartet wird int. Wenn der Compiler den Maschinencode generiert, geht dieser Maschinencode davon aus, dass er immer dieses einzelne ganzzahlige Argument empfängt. Es ist nicht erforderlich, den Typ zur Laufzeit anzugeben.
John Bode
Ich kann mit putchar () Alphabete drucken.
user106313
2
@ user31782: printfformatiert die gesamte Ausgabe als Text (ASCII oder anderweitig); Der Konvertierungsspezifizierer gibt an, wie die Ausgabe formatiert werden soll. printf( "%d\n", 65 );schreibt die Zeichenfolge '6' und '5'in die Standardausgabe, da der %dKonvertierungsspezifizierer anweist, das entsprechende Argument als Dezimalzahl zu formatieren. printf( "%c\n", 65 );schreibt das Zeichen 'A'in die Standardausgabe, da es %canweist printf, das Argument als Zeichen aus dem Ausführungszeichensatz zu formatieren.
John Bode
1
@ user31782: Nicht ohne Änderung der Sprachdefinition. Sicher, es ist möglich (C ++ ist ebenfalls statisch typisiert, kann jedoch Typen für die Operatoren <<und >>E / A ableiten ), aber es würde der Sprache eine gewisse Komplexität hinzufügen. Trägheit ist manchmal schwer zu überwinden.
John Bode
8

Denn in dem Moment printf, in dem der Compiler aufgerufen wird und seine Arbeit erledigt, ist er nicht mehr da, um ihm mitzuteilen, was zu tun ist.

Die Funktion erhält keine Informationen außer den Parametern, und die vararg-Parameter haben keinen Typ. Sie printfhätte also keine Ahnung, wie sie gedruckt werden sollen, wenn sie keine expliziten Anweisungen über die Formatzeichenfolge erhalten. Der Compiler könnte (normalerweise) ableiten, welcher Typ jedes Argument ist, aber Sie müssten immer noch eine Formatzeichenfolge schreiben, um anzugeben, wo jedes Argument relativ zum konstanten Text gedruckt werden soll. Vergleiche "$%d"und "%d$"; Sie machen verschiedene Dinge und der Compiler kann nicht erraten, was Sie wollen. Da Sie ohnehin manuell ein Format - String zu komponieren haben Argument angeben Positionen , dann ist es offensichtlich , dass die Wahl die Aufgabe , unter Angabe der Argumentation abzuladen Typen als auch für den Anwender.

Die Alternative wäre, dass der Compiler die Formatzeichenfolge nach Positionen durchsucht, dann die Typen ableitet, die Formatzeichenfolge neu schreibt, um die Typinformationen hinzuzufügen, und stattdessen die geänderte Zeichenfolge in Ihre Binärdatei kompiliert. Dies würde jedoch nur für Zeichenfolgen im Literalformat funktionieren . C erlaubt auch dynamisch zugewiesene Formatzeichenfolgen, und es würde immer Fälle geben, in denen der Compiler nicht genau rekonstruieren kann, wie die Formatzeichenfolge zur Laufzeit aussehen wird. (Manchmal möchten Sie auch etwas als einen anderen, verwandten Typ drucken und so eine Verengung effektiv durchführen. Dies kann auch kein Compiler vorhersagen.)

Kilian Foth
quelle
Die Funktion printf () weiß also, dass "x" eine Variable ist, aber sie weiß nicht, welchen Typ sie hat, aber der Compiler weiß es. Können wir printf () nicht aktualisieren, damit es den Datentyp kennt? Außerdem erinnere ich mich, dass "cout" in C ++ Daten drucken kann, indem es automatisch ihren Typ kennt.
user106313
@ user31782 C Funktionsaufrufe sind extrem einfach. Alles, was übergeben printf()wird, ist ein Zeiger auf die Formatzeichenfolge und ein Zeiger auf den Puffer, in dem sich die Argumente befinden. Nicht einmal die Länge dieses Puffers wird übergeben! Dies ist einer der Gründe, warum C so viel schneller sein kann als andere Sprachen. Was Sie vorschlagen, sind Größenordnungen komplexer.
Grahamparks
@grahamparks Ist "cout" in C ++ langsamer als "printf". Würde es langsam werden, printf () wie "cout" zu machen?
user106313
3
@ user31782: Nicht unbedingt langsamer zur Laufzeit (dies hängt wie üblich ab), erfordert jedoch Sprachfunktionen, die in C einfach nicht vorhanden sind. C hat keine Funktionsüberladung, geschweige denn die coutin C ++ verwendeten Vorlagenmechanismen .
Mat
5
Ja, aber Sie sollten sich daran erinnern, dass C aus dem Jahr 1972 stammt. Diese Funktionen wurden erst viel später erfunden .
Kilian Foth
5

printf()ist eine so genannte variadische Funktion , die eine variable Anzahl von Argumenten akzeptiert.

Variadische Funktionen in C verwenden einen speziellen Prototyp, um dem Compiler mitzuteilen, dass die Liste der Argumente eine unbekannte Länge hat:

int printf(const char *format, ...);

Standard C bietet eine Reihe von Funktionen stdarg.h, mit denen die Argumente einzeln abgerufen und in einen bestimmten Typ umgewandelt werden können. Dies bedeutet, dass verschiedene Funktionen den Typ jedes Arguments selbst bestimmen müssen. printf()trifft diese Entscheidung basierend auf dem Format der Zeichenfolge.

Dies ist eine grobe Vereinfachung der printf()tatsächlichen Funktionsweise, aber der Prozess läuft folgendermaßen ab :

int printf(const char *format, ...) {

    /* Get ready to process arguments that follow 'format' */
    va_list ap;
    va_start(ap, format);

    /* Deep in the function, something that's dissected the
       format string has decided that the next argument is a
       string.  Grab the next argument, cast it to char * and
       write it to wherever it should go.
     */
    char *string = va_arg(ap, char *);
    write_string_to_output(string);

    /* Conclude processing of arguments */
    va_end(ap);
}

Der gleiche Vorgang findet für alle Typen statt printf(), die konvertiert werden können. Sie können ein Beispiel dafür im Quellcode für die OpenBSD-Implementierung von sehen vfprintf(), der Funktion, die zugrunde liegt printf().

Einige C-Compiler sind intelligent genug, um Aufrufe zu erkennen printf(), die Formatzeichenfolge auszuwerten, wenn sie eine Konstante ist, und zu überprüfen, ob die Typen der übrigen Argumente mit den angegebenen Konvertierungen kompatibel sind. Dieses Verhalten ist nicht erforderlich, weshalb der Standard weiterhin die Angabe des Typs als Teil der Formatzeichenfolge erfordert. Bevor diese Art von Überprüfungen durchgeführt wurde, führten Fehlanpassungen zwischen der Formatzeichenfolge und der Argumentliste einfach zu einer falschen Ausgabe.

In C ++ <<handelt es sich um einen Operator, der coutbeispielsweise cout << foo << bareinen Infix-Ausdruck verwendet, der zur Kompilierungszeit auf Richtigkeit überprüft und in Code umgewandelt werden kann, der die rechten Ausdrücke in etwas umwandelt, mit dem er coutumgehen kann.

Blrfl
quelle
3

Die Designer von C wollten den Compiler so einfach wie möglich gestalten. Es wäre zwar möglich gewesen, E / A in ähnlicher Weise wie in anderen Sprachen zu handhaben, und es wäre erforderlich, dass der Compiler die E / A-Routine automatisch mit Informationen über die Arten der übergebenen Parameter versorgt, und dies könnte in vielen Fällen der Fall sein Dies ermöglicht effizienteren Code als mit printf(*) möglich, da die Definition von Dingen auf diese Weise den Compiler komplizierter gemacht hätte.

In den frühesten Tagen von C wusste Code, der eine Funktion aufrief, weder, noch kümmerte es ihn, welche Argumente er erwartete. Jedes Argument würde je nach Typ eine bestimmte Anzahl von Wörtern auf den Stapel verschieben, und die Funktionen würden erwarten, dass unterschiedliche Parameter im obersten, zweitletzten usw. Stapelschlitz unterhalb der Rücksprungadresse gefunden werden. Wenn eine printfMethode herausfinden konnte, wo sich ihre Argumente auf dem Stapel befinden, konnte der Compiler sie nicht anders behandeln als jede andere Methode.

In der Praxis wird das von C vorgesehene Muster der Parameterübergabe nur noch sehr selten verwendet, außer wenn verschiedene Funktionen wie aufgerufen werden printfund wenn printfspezielle Konventionen für die Parameterübergabe verwendet wurden [z. B. wenn der erste Parameter vom Compiler generiert wird const char*und automatisch generiert wird Informationen über die zu übergebenden Typen], hätten Compiler besseren Code dafür generieren können (unter anderem ohne Ganzzahl- und Gleitkomma-Heraufstufungen).] Leider sehe ich keine Wahrscheinlichkeit, dass ein Compiler Funktionen hinzufügt Compiler melden Variablentypen an aufgerufenen Code.

Ich finde es merkwürdig, dass Nullzeiger aufgrund ihrer Nützlichkeit als "Milliarden-Dollar-Fehler" angesehen werden und dass sie im Allgemeinen nur in Sprachen, die keine Nullzeigerarithmetik und Zugriffe einfangen, ein sehr schlechtes Verhalten verursachen. Ich würde den Schaden, der durch printfund nullterminierte Zeichenfolgen verursacht wird, als viel schlimmer betrachten.

Superkatze
quelle
0

Stellen Sie sich vor, Sie übergeben Variablen an eine andere von Ihnen definierte Funktion. Normalerweise teilen Sie der anderen Funktion mit, welche Art von Daten erwartet / empfangen werden soll. Genauso mit printf(). Es ist bereits in der stdio.hBibliothek definiert und erfordert, dass Sie angeben, welche Daten es empfängt, damit es im richtigen Format ausgegeben werden kann (wie in Ihrem Fall int).

Hammerton Mwawuda
quelle