Ich habe die folgende Funktion, die übergebene Argumente in eine Binärdatei schreibt.
void writeFile(FILE *fp, const int numOfChars, ...)
{
va_list ap;
va_start(ap, numOfChars);
for(int i = 0; i < numOfChars; i++)
{
const char c = va_arg(ap, char);
putc(c, fp);
}
va_end(ap);
}
Beim Kompilieren erhalte ich die folgende Warnung vom Compiler
warning: second argument to 'va_arg' is of promotable type 'char'; this va_arg
has undefined behavior because arguments will be promoted to 'int' [- Wvarargs]
Jetzt, wie ich es verstehe, möchte C den Typ char zu int befördern. Warum will C das tun? Zweitens, ist die beste Lösung, um das int zurück in ein Zeichen zu werfen?
Jetzt, wie ich es verstehe, möchte C den Typ char zu int befördern. Warum will C das tun?
Denn so steht es in der Norm. Wenn Sie einen ganzzahligen Wert übergeben, dessen Conversion-Rang kleiner als der von ist int
(z.B char
, bool
oder short
) in eine Funktion mit einer variablen Anzahl von Argumenten, in die sie umgewandelt wird int
. Vermutlich hat dies seine Wurzeln in der Leistung, wo es besser war (und tatsächlich oft noch heute ist), Werte zu übergeben, die an einer Maschinenwortgrenze ausgerichtet sind.
Zweitens, ist die beste Lösung, um das int zurück in ein Zeichen zu werfen?
Ja, aber Sie brauchen nicht einmal eine Besetzung, eine implizite Konvertierung reicht aus:
char ch = va_arg(ap, int);
Variadische Funktionen werden speziell behandelt.
Bei einer nicht-variadischen Funktion gibt der Prototyp (Deklaration) die Typen aller Parameter an. Parameter können von jedem Typ (kein Array, keine Funktion) sein – einschließlich Typen, die schmaler als sind int
.
Bei einer Variadic-Funktion kennt der Compiler die Typen der entsprechenden Parameter nicht , ...
. Aus historischen Gründen und um die Arbeit des Compilers zu erleichtern, sind alle entsprechenden Argumente von Typen schmaler als int
dazu befördert werden int
oder zu unsigned int
und alle Argumente vom Typ float
dazu befördert werden double
. (Deshalb printf
verwendet für beide die gleichen Formatbezeichner float
oder double
Argumente.)
Also eine variadische Funktion kann nicht empfangen Argumente des Typs char
. Sie können eine solche Funktion mit a aufrufen char
Argument, aber es wird gefördert werden int
.
(In frühen Versionen von C, bevor Prototypen eingeführt wurden, alle Funktionen haben sich so verhalten. Sogar C11 erlaubt Nicht-Prototyp-Deklarationen, in denen enge Argumente gefördert werden int
, unsigned int
oder double
. Aber angesichts der Existenz von Prototypen gibt es wirklich keinen Grund, Code zu schreiben, der von solchen Beförderungen abhängt – mit Ausnahme des Sonderfalls variadischer Funktionen.)
Aus diesem Grund hat es keinen Sinn, es zu haben va_arg()
annehmen char
als Typargument.
Aber die Sprache nicht verbieten eine solche Berufung auf va_arg()
; in der Tat den Abschnitt der Norm beschreibt <stdarg.h>
erwähnt keine Argumentförderung. Die Regel ist im Abschnitt über Funktionsaufrufe angegeben, N1570 6.5.2.2 Absatz 7:
Wenn der Ausdruck, der die aufgerufene Funktion bezeichnet, einen Typ hat, der einen Prototyp enthält, werden die Argumente implizit wie durch Zuweisung in die Typen der entsprechenden Parameter konvertiert, wobei der Typ jedes Parameters die nicht qualifizierte Version seiner Deklaration ist Typ. Die Ellipsennotation in einem Funktionsprototypdeklarator bewirkt, dass die Argumenttypkonvertierung nach dem letzten deklarierten Parameter angehalten wird. Die Heraufstufung von Standardargumenten wird für nachgestellte Argumente durchgeführt.
Und die Beschreibung der va_arg()
Makro, 7.16.1.1, sagt (Hervorhebung hinzugefügt):
Wenn es kein tatsächliches nächstes Argument gibt oder wenn der Typ nicht mit dem Typ des tatsächlichen nächsten Arguments kompatibel ist (wie nach dem Standardargument promotions befördert), ist das Verhalten undefiniert, mit Ausnahme der folgenden Fälle:
[SNIP]
Die „Default Argument Promotions“ wandeln schmale Argumente um int
, unsigned int
oder double
. (Ein Argument eines vorzeichenlosen ganzzahligen Typs, dessen Maximalwert überschreitet INT_MAX
werden befördert unsigned int
. Es ist theoretisch möglich für char
sich so zu verhalten, aber nur in einer sehr ungewöhnlichen Implementierung.)
Zweitens, ist die beste Lösung, um das int zurück in ein Zeichen zu werfen?
Nein, in diesem Fall nicht. Gipsverbände sind selten erforderlich; In den meisten Fällen können implizite Konvertierungen die gleiche Aufgabe erfüllen. In diesem speziellen Fall:
const char c = va_arg(ap, char);
putc(c, fp);
das erste Argument zu putc
ist schon typ int
also besser geschrieben als:
const int c = va_arg(ap, int);
putc(c, fp);
Das int
Wert wird umgewandelt durch putc
zu unsigned char
und angeschrieben fp
.