Wie drucke ich Typen unbekannter Größe wie ino_t?

Lesezeit: 12 Minuten

Benutzer-Avatar
fuz

Ich erlebe oft Situationen, in denen ich mit drucken möchte printf der Wert eines Integer-Typs mit implementierungsdefinierter Größe (wie z ino_t oder time_t). Im Moment verwende ich dafür ein Muster wie dieses:

#include <inttypes.h>

ino_t ino; /* variable of unknown size */
printf("%" PRIuMAX, (uintmax_t)ino);

Dieser Ansatz funktioniert bisher, hat aber einige Nachteile:

  • Ich muss wissen, ob der Typ, den ich drucken möchte, signiert oder unsigniert ist.
  • Ich muss eine Typumwandlung verwenden, die meinen Code möglicherweise vergrößert.

Gibt es eine bessere Strategie?

  • Ähnliche Frage hier: stackoverflow.com/questions/1401526/…

    –Martin R

    19. Juli 2014 um 21:11 Uhr

  • @MartinR – In Bezug auf die ähnliche Frage, auf die Sie sich bezogen haben, stimmen Sie zu. Und auch für diese ist die dortige Lösung perfekt geeignet. Aber diese Version der Frage wird so gestellt, dass sie eine viel klarere Darstellung des Problems bietet. Schade, dass es keine ethische Möglichkeit gibt, den Inhalt der anderen Antwort hier zu veröffentlichen.

    – ryker

    19. Juli 2014 um 21:42 Uhr

  • C++ bietet eine einfache Lösung: std::cout << ino ;. Der angemessene << Operatorüberladung für den Argumenttyp wird verwendet – keine Formatbezeichner erforderlich. Wenn es keinen zwingenden Grund gibt, C zu verwenden, und dies ein häufiger Bedarf ist, kann C++ nützlich sein, selbst wenn Sie nicht beabsichtigen, OO-Code zu schreiben. Zum größten Teil muss sich Ihr C-Code überhaupt nicht ändern, außer wenn C++ eine bessere Mausefalle bietet – das Überladen von Operatoren ist in diesem Fall eine davon.

    – Clifford

    19. Juli 2014 um 21:51 Uhr


  • @Clifford: “Verwenden Sie eine andere Sprache” ist keine gültige Antwort / kein gültiger Rat zur Lösung eines Problems in einer bestimmten Sprache. Es gibt viel von wichtigen C-Dingen, die in C++ nicht funktionieren, und selbst wenn sie es tun, ist idiomatischer C-Code Anti-idiomatisches C++, als C++ höchst verpönt, und wahrscheinlich viele falsche Warnungen oder Fehler (oder noch schlimmer, subtile Unterschiede) zu erzeugen Verhalten), wenn es als C++ behandelt wird.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    20. Juli 2014 um 1:02 Uhr

  • @FUZxxl – Ja, interessant. Der Kommentar des Beantworters dieser akzeptierten Antwort: Es ist frustrierend, aber ich kenne keinen besseren Weg. drückt ähnliche Bedenken aus, die Sie in dieser Frage äußern, dh dass es möglich ist, aber die Methode nicht optimal ist. Ich glaube, Ihre gestellte Frage ist sehr gut dargestellt. Offensichtlich stimmen andere der Anzahl der Upclicks zu.

    – ryker

    20. Juli 2014 um 17:39 Uhr


#include <inttypes.h>
ino_t ino; /* variable of unknown size */
/* ... */
printf("%" PRIuMAX, (uintmax_t)ino);

Das wird sicherlich funktionieren (mit einigen Vorbehalten; siehe unten), aber ich würde verwenden:

printf("%ju", (uintmax_t)ino);

Das j Längenmodifikator

Gibt an, dass eine folgende d, i, o, u,
xoder X Konvertierungsspezifizierer gilt für eine intmax_t

oder uintmax_t Streit; oder dass ein folgendes n Konvertierungsbezeichner gilt für einen Zeiger auf eine intmax_t Streit.

Es gibt auch z und t Modifikatoren für size_t und ptrdiff_t (und ihre entsprechenden signierten/unsignierten Typen).

Und persönlich finde ich das Format String-Makros definiert in <inttypes.h> hässlich und schwer zu merken, weshalb ich es bevorzuge "%ju" oder "%jd".

Wie Sie bereits erwähnt haben, ist es hilfreich zu wissen, ob der Typ (ino_t in diesem Fall) signiert oder unsigniert ist. Wenn Sie das nicht wissen, können Sie es herausfinden:

#include <stdio.h>
#include <stdint.h>
#include <sys/types.h>

#define IS_SIGNED(type) ((type)-1 < (type)0)
#define DECIMAL_FORMAT(type) (IS_SIGNED(type) ? "%jd" : "%ju")
#define CONVERT_TO_MAX(type, value) \
    (IS_SIGNED(type) ? (intmax_t)(value) : (uintmax_t)(value))
#define PRINT_VALUE(type, value) \
    (printf(DECIMAL_FORMAT(type), CONVERT_TO_MAX(type, (value))))

int main(void) {
    ino_t ino = 42;
    PRINT_VALUE(ino_t, ino);
    putchar('\n');
}

obwohl das vielleicht übertrieben ist. Wenn Sie sicher sind, dass der Typ schmaler als 64 Bit ist, können Sie den Wert in konvertieren intmax_t, und der Wert bleibt erhalten. Oder Sie können verwenden uintmax_t und erhalten Sie gut definierte Ergebnisse für alle Werte, obwohl sie gedruckt werden -1 wie 18446744073709551615 (264-1) kann etwas verwirrend sein.

All dies funktioniert nur, wenn Ihre C-Implementierung dies unterstützt <stdint.h> und die j Längenmodifikator für printf – dh, wenn es C99 unterstützt. Nicht alle Compiler tun dies (HustenMicrosoftHusten). Für C90 sind die breitesten Integer-Typen long und unsigned longund Sie können in diese konvertieren und verwenden "%ld" und/oder "%lu". Sie können die C99-Konformität theoretisch mit der testen __STDC_VERSION__ vordefiniertes Makro — obwohl einige Pre-C99-Compiler möglicherweise noch Typen unterstützen, die größer als sind long und unsigned long als Erweiterung.

  • Aus meiner Lektüre von pubs.opengroup.org/onlinepubs/009696699/basedefs/sys/… ino_t ist garantiert ein arithmetischer Typ, aber es gibt keine Garantie, dass es sich um einen ganzzahligen Typ handelt. Daher können sowohl PRIuMAX als auch PRIdMAX ungeeignet sein.

    – James Youngmann

    26. Oktober 2015 um 17:50 Uhr

  • @JamesYoungman: Weiter unten auf derselben Seite: “fsblkcnt_t, fsfilcnt_tund ino_t sind als vorzeichenlose Integer-Typen zu definieren.”

    – Keith Thompson

    26. Oktober 2015 um 18:25 Uhr

  • Keith, das ist aber eine XSI-Erweiterung.

    – James Youngmann

    12. Januar 2016 um 23:35 Uhr

  • @JamesYoungman Es ist nur eine XSI-Erweiterung für fsblkcnt_t und fsfilcnt_t. Für ino_t gilt immer, dass es als vorzeichenloser Integer-Typ definiert ist.

    – Pim

    4. Januar 2020 um 10:24 Uhr


Benutzer-Avatar
Arkku

Mit generischen Makros vom Typ C11 ist es möglich, die Formatzeichenfolge zur Kompilierzeit zu erstellen, z. B.:

#include <inttypes.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>

#define PRI3(B,X,A) _Generic((X), \
                             unsigned char: B"%hhu"A, \
                             unsigned short: B"%hu"A, \
                             unsigned int: B"%u"A, \
                             unsigned long: B"%lu"A, \
                             unsigned long long: B"%llu"A, \
                             signed char: B"%hhd"A, \
                             short: B"%hd"A, \
                             int: B"%d"A, \
                             long: B"%ld"A, \
                             long long: B"%lld"A)
#define PRI(X) PRI3("",(X),"")
#define PRIFMT(B,X,A) PRI3(B,(X),A),(X)

int main () {
    signed char sc = SCHAR_MIN;
    unsigned char uc = UCHAR_MAX;
    short ss = SHRT_MIN;
    unsigned short us = USHRT_MAX;
    int si = INT_MIN;
    unsigned ui = UINT_MAX;
    long sl = LONG_MIN;
    unsigned long ul = ULONG_MAX;
    long long sll = LLONG_MIN;
    unsigned long long ull = ULLONG_MAX;
    size_t z = SIZE_MAX;
    intmax_t sj = INTMAX_MIN;
    uintmax_t uj = UINTMAX_MAX;

    (void) printf(PRIFMT("signed char       : ", sc, "\n"));
    (void) printf(PRIFMT("unsigned char     : ", uc, "\n"));
    (void) printf(PRIFMT("short             : ", ss, "\n"));
    (void) printf(PRIFMT("unsigned short    : ", us, "\n"));
    (void) printf(PRIFMT("int               : ", si, "\n"));
    (void) printf(PRIFMT("unsigned int      : ", ui, "\n"));
    (void) printf(PRIFMT("long              : ", sl, "\n"));
    (void) printf(PRIFMT("unsigned long     : ", ul, "\n"));
    (void) printf(PRIFMT("long long         : ", sll, "\n"));
    (void) printf(PRIFMT("unsigned long long: ", ull, "\n"));
    (void) printf(PRIFMT("size_t            : ", z, "\n"));
    (void) printf(PRIFMT("intmax_t          : ", sj, "\n"));
    (void) printf(PRIFMT("uintmax_t         : ", uj, "\n"));
}

Es gibt jedoch ein potenzielles Problem: Wenn es Typen gibt, die sich von den aufgelisteten unterscheiden (dh andere als signed und unsigned Versionen von char, short, int, longund long long), funktioniert dies bei diesen Typen nicht. Es ist auch nicht möglich, Typen hinzuzufügen, wie z size_t und intmax_t auf den Typ generisches Makro “nur für den Fall”, weil es einen Fehler verursacht, wenn sie sind typedefd von einem der bereits aufgeführten Typen. Ich habe die verlassen default Groß-/Kleinschreibung nicht angegeben, sodass das Makro einen Kompilierzeitfehler generiert, wenn kein übereinstimmender Typ gefunden wird.

Wie jedoch im Beispielprogramm zu sehen ist, size_t und intmax_t funktionieren gut auf Plattformen, auf denen sie mit einem der aufgelisteten Typen identisch sind (z. B. gleich wie long). Ebenso ist es kein Problem, wenn z. long und long longoder long und int, sind vom gleichen Typ. Aber eine sicherere Version könnte darin bestehen, einfach zu casten intmax_t oder uintmax_t nach Vorzeichen (wie in anderen Antworten zu sehen) und erstellen Sie ein Typ-generisches Makro mit nur diesen Optionen …

Ein kosmetisches Problem besteht darin, dass das typgenerische Makro mit Klammern um das Zeichenfolgenliteral erweitert wird, wodurch eine Verkettung mit benachbarten Zeichenfolgenliteralen auf die übliche Weise verhindert wird. Dies verhindert Dinge wie:

(void) printf("var = " PRI(var) "\n", var); // does not work!

Daher die PRIFMT Makro mit Präfix und Suffix für den allgemeinen Fall des Druckens einer einzelnen Variablen:

(void) printf(PRIFMT("var = ", var, "\n"));

(Beachten Sie, dass es einfach wäre, dieses Makro mit den nicht ganzzahligen Typen zu erweitern, die von unterstützt werden printfz.B, double, char *…)

Benutzer-Avatar
Pablo1977

Die “Größe” eines Integer-Typs ist hier nicht relevant, sondern sein Wertebereich.

Wie du anscheinend noch versucht hast, ist es möglich zu casten uintmax_t und intmax_t Unklarheiten in der leicht zu lösen printf() Anruf.

Das Problem der signierten oder unsignierten Typen kann auf einfache Weise gelöst werden:

  • Alle Ganzzahloperationen ohne Vorzeichen funktionieren modulo “N” für einen positiven Wert N, abhängig vom Typ. Dies impliziert, dass jedes Ergebnis, das nur vorzeichenlose Integer-Typen enthält, einen nicht negativen Wert ergibt.
  • Um zu erkennen, ob eine Variable x einen signierten oder einen unsignierten Typ hat, reicht es aus zu prüfen, ob x und -x sind beides nichtnegative Werte.

Zum Beispiel:

 if ( (x>=0) && (-x>=0) )
    printf("x has unsigned type");
 else
    printf("x has signed type");

Jetzt können wir einige Makros schreiben:

(Bearbeitet: Name und Ausdruck des Makros haben sich geändert)

 #include <inttypes.h>
 #include <limits.h>

 #define fits_unsigned_type(N) ( (N >= 0) && (  (-(N) >= 0) || ((N) <= INT_MAX) ) )
 #define smartinteger_printf(N) \
     (fits_unsigned_type(N)? printf("%ju",(uintmax_t)(N)): printf("%jd",(intmax_t) (N)) )
// ....
ino_t x = -3;
printf("The value is: "); 
smartinteger_printf(x);
//.....

Notiz: Das vorzeichenbehaftete oder vorzeichenlose Zeichen einer Variablen wird vom obigen Makro nicht gut erkannt, wenn der Wert 0 ist. Aber in diesem Fall funktioniert alles gut, weil 0 die gleiche Bitdarstellung in vorzeichenbehafteten oder vorzeichenlosen Typen hat.

Das erste Makro kann verwendet werden, um zu erkennen, ob der zugrunde liegende Typ eines arithmetischen Objekts einen Typ ohne Vorzeichen hat oder nicht.
Dieses Ergebnis wird im zweiten Makro verwendet, um auszuwählen, wie das Objekt auf dem Bildschirm gedruckt wird.

1. NEUAUFLAGE:

  • Wie Pascal Cuoq wies in seinem Kommentar darauf hin, dass die Integer-Promotions für unsigned berücksichtigt werden müssen char und short Werte im Bereich von passen int. Dies entspricht der Abfrage, ob der Wert im Bereich 0 bis liegt INT_MAX.

Also habe ich den Namen des Makros in geändert fits_signed_type.
Außerdem habe ich das Makro modifiziert, um das Positive zu berücksichtigen int Werte.

Das Makro fits_unsigned_type kann in den meisten Fällen feststellen, ob ein Objekt einen vorzeichenlosen ganzzahligen Typ hat oder nicht.

  • Wenn der Wert negativ ist, ist der Typ offensichtlich nicht unsigned.
  • Wenn der Wert N dann positiv ist
    • wenn -N positiv ist, dann hat N unsigned Typ,
    • Wenn -N negativ ist, aber N im Bereich von 0 bis liegt INT_MAXdann die Art von N könnte sein signed oder unsignedaber es würde in den Bereich positiver Werte von passenintdie in den Bereich von passt uintmax_t.

2. NEUAUFLAGE:

Ir scheint, dass es hier zwei Ansätze gibt, das gleiche Problem zu lösen. Mein Ansatz berücksichtigt die Wertebereich und Heraufstufungsregeln für ganze Zahlen um den korrekten gedruckten Wert mit zu erzeugen printf(). Auf der anderen Seite bestimmt die Herangehensweise von Grzegorz Szpetkowski die vorzeichenbehaftetes Zeichen eines Typs in gerader Form. Ich mag beides.

  • Ihr Makro has_unsigned_type macht nicht das, was der Name verspricht unsigned char und unsigned short wenn sie nur Werte enthalten, die zu einem passen intverhindert aber nicht, dass die Funktion funktioniert.

    – Pascal Cuoq

    19. Juli 2014 um 22:09 Uhr


  • Du bist richtig. Ich habe für den Wert 0 herausgefunden, aber positive Werte sind in der gleichen Betrachtung. Das Makro soll “etwas” in der richtigen Wahl erkennen printf() Zeichenfolge formatieren. Ich werde Ihren Kommentar hinzufügen und wahrscheinlich den Namen des Makros ändern.

    – pablo1977

    19. Juli 2014 um 22:23 Uhr

  • Sie können die Vorzeichen eines Ausdrucks (mindestens vom Rang) bestimmen int) ohne es zu bewerten über (1?-1:(expr))<0).

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    20. Juli 2014 um 1:00 Uhr

  • @R..: Schön. Auf so eine einfache Lösung bin ich nicht gekommen.

    – pablo1977

    20. Juli 2014 um 2:18 Uhr

  • @ryyker Ich denke du meinst Google-artig.

    – Jon McClung

    20. März 2019 um 15:16 Uhr

Benutzer-Avatar
Grzegorz Szpetkowski

Da Sie bereits C99-Header verwenden, besteht die Möglichkeit, je nach Angabe des genauen Breitenformats zu verwenden sizeof(T) und signierter/unsignierter Scheck. Dies muss jedoch nach der Vorverarbeitungsphase erfolgen (so traurig ## -Operator kann hier nicht zum Erstellen des PRI-Tokens verwendet werden). Hier ist eine Idee:

#include <inttypes.h>

#define IS_SIGNED(T) (((T)-1) < 0) /* determines if integer type is signed */

...

const char *fs = NULL;
size_t bytes = sizeof(T);

if (IS_SIGNED(T))
    switch (bytes) {
        case 1: fs = PRId8;  break;
        case 2: fs = PRId16; break;
        case 4: fs = PRId32; break;
        case 8: fs = PRId64; break;
    }
else
    switch (bytes) {
        case 1: fs = PRIu8;  break;
        case 2: fs = PRIu16; break;
        case 4: fs = PRIu32; break;
        case 8: fs = PRIu64; break;
    }

Mit dieser Methode wird kein Cast mehr benötigt, jedoch muss der Formatstring manuell erstellt werden, bevor er an übergeben wird printf (dh keine automatische Zeichenfolgenverkettung). Hier ist ein funktionierendes Beispiel:

#include <stdio.h>
#include <inttypes.h>

#define IS_SIGNED(T) (((T)-1) < 0)

/* using GCC extension: Statement Expr */
#define FMT_CREATE(T) ({                      \
    const char *fs = NULL;                    \
    size_t bytes = sizeof(ino_t);             \
                                              \
    if (IS_SIGNED(T))                         \
        switch (bytes) {                      \
            case 1: fs = "%" PRId8;  break;   \
            case 2: fs = "%" PRId16; break;   \
            case 4: fs = "%" PRId32; break;   \
            case 8: fs = "%" PRId64; break;   \
        }                                     \
    else                                      \
        switch (bytes) {                      \
            case 1: fs = "%" PRIu8;  break;   \
            case 2: fs = "%" PRIu16; break;   \
            case 4: fs = "%" PRIu32; break;   \
            case 8: fs = "%" PRIu64; break;   \
        }                                     \
    fs;                                       \
})

int main(void) {
    ino_t ino = 32;

    printf(FMT_CREATE(ino_t), ino); putchar('\n');

    return 0;
}

Beachten Sie, dass dies einige kleine Tricks erfordert Aussage Expraber es könnte auch einen anderen Weg geben (dies ist der “Preis”, um generisch zu sein).

BEARBEITEN:

Hier ist die zweite Version, die keine spezifische Compiler-Erweiterung erfordert (keine Sorge, ich kann sie auch nicht lesen), die ein funktionsähnliches Makro verwendet:

#include <stdio.h>
#include <inttypes.h>

#define IS_SIGNED(T) (((T)-1) < 0)
#define S(T) (sizeof(T))

#define FMT_CREATE(T)   \
    (IS_SIGNED(T)        \
        ? (S(T)==1?"%"PRId8:S(T)==2?"%"PRId16:S(T)==4?"%"PRId32:"%"PRId64) \
        : (S(T)==1?"%"PRIu8:S(T)==2?"%"PRIu16:S(T)==4?"%"PRIu32:"%"PRIu64))

int main(void)
{
    ino_t ino = 32;

    printf(FMT_CREATE(ino_t), ino);
    putchar('\n');

    return 0;
}

Beachten Sie, dass der Bedingungsoperator eine linke Assoziativität aufweist (daher wird er wie beabsichtigt von links nach rechts ausgewertet).

1335220cookie-checkWie drucke ich Typen unbekannter Größe wie ino_t?

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

Privacy policy