Forum: Compiler & IDEs Suffix bei konstanten Werten


von Thomas L. (thomasblue)


Lesenswert?

Hallo zusammen,

ich habe schon öfter im Code das Suffix U (unsigned) hinter konstanten 
Werten gesehen, also z.B.:

int tmp;
tmp = 15U;

Was hat die Kennzeichnung als unsigned für einen Sinn? Macht der GCC 
dann spezielle Operationen? Wie sieht das mit L (Long) aus?

Viele Grüße

von Krapao (Gast)


Lesenswert?

In deinem Beispiel macht es keinen Sinn.

Der Suffix ist optional und nicht GCC spezifisch, sondern z.B. im C99 
Standard enthalten. Er spezifiziert den Typ einer Ganzzahlkonstanten und 
hat Auswirkungen darauf, wie ein Ausdruck mit dieser Konstanten 
ausgewertet wird. Im einfachsten Fall ersetzt es einen Cast.

von Klaus W. (mfgkw)


Lesenswert?

Beispiel, wo es einen Unterschied macht:
Auf einem Rechner mit 4 Byte für int könnte man folgendes schreiben:
1
#include <stdlib.h>
2
#include <stdio.h>
3
4
5
int main( int nargs, char **args )
6
{
7
    unsigned long long   ul = 4000*1000000;
8
9
    printf( "%llu\n", ul );
10
11
    return 0;
12
}
Das ergibt fälschlicherweise (Ubuntu, gcc):
18446744073414584320

Grund: sowohl 4000 als auch 1000000 sind (signed) int, also wird die 
Berechnung in int ausgeführt. Das Produkt daraus überschreitet den 
Wertebereich aber, und zwar gerade so, daß das höchste Bit gesetzt ist 
(hexadezimal: 0xee6b2800).
Das Ergebnis wird zur Zuweisung auf 8 Byte aufgeblasen, und weil es eine 
int mit gesetztem Bit ist, wird dieses vermeintliche Vorzeichenbit auf 
die neuen 4 Byte aufgezogen (0xffffffffee6b2800).
Mit printf als unsigned long long ausgegeben wird dann eine viel zu 
große positive Zahl daraus.


Andere Variante:
1
#include <stdlib.h>
2
#include <stdio.h>
3
4
5
int main( int nargs, char **args )
6
{
7
    unsigned long long   ul = 4000u*1000000u;
8
9
    printf( "%llu\n", ul );
10
11
    return 0;
12
}
Das ergibt jetzt korrekt:
4000000000

Jetzt wird nämlich ein Produkt aus zwei unsigned berechnet, ebenfalls 
wieder als Bitmuster 0xee6b2800. Weil dem Compiler aber klar ist, daß es 
unsigned ist, wird beim Erweitern auf 8 Byte das oberste Bit ignoriert 
und nur 0x00... vorne ergänzt. Das resultierende 0x00000000ee6b2800 ist 
in dezimal die erwartete 4000000000.

von Klaus W. (mfgkw)


Lesenswert?

Bei 2 Byte für int könnte man ein ähnliches Beispiel mit 40*1000 
konstruieren (überschreitet das maximal mögliche 32767 von signed int 
etwas, sodaß das oberste Bit 1 wird im Produkt).

von Thomas L. (thomasblue)


Lesenswert?

Super Antwort, danke schön!
Hatte mir so etwas in die Richtung schon gedacht. Aber bei Werten die 
als Zustandsbeschreibung genutzt werden, hat es dann keinen tieferen 
Sinn. Also zeugt das nur von einem gutem Programmierstil, wenn man es 
einfach immer benutzt.

von Klaus W. (mfgkw)


Lesenswert?

im Prinzip ja, aber bei deinem Beispiel erscheint es mir eher zweckfrei 
- wie bereits von Krapao festgestellt:

> int tmp;
> tmp = 15U;

von Peter D. (peda)


Lesenswert?

Bei einer Zuweisung ist der Suffix unnötig.

Er wird nur benötigt, um eine Berechnung in einem bestimmten Format 
durchzuführen, z.B. 32bit Schieben oder unsigned Division.


Peter

von ./. (Gast)


Lesenswert?

Peter Dannegger schrieb:
> Bei einer Zuweisung ist der Suffix unnötig.

Wenn man darauf Wert legt, daß der Compiler z.B. nur die Routinen
für float-Berechnungen und nicht für double-Arithmetik benutzt,
ist es sinnvoll auch bei Zuweisungen einen Suffix zu verwenden.
Das trifft im weiteren auch auf Konstanten in Berechnungen zu.


float pi;

pi=3.1415926F;

grad=3.1415926F*rad/4.0F;

von Peter D. (peda)


Lesenswert?

./. schrieb:
> ist es sinnvoll auch bei Zuweisungen einen Suffix zu verwenden.

Warum?

Eine Zuweisung erfolgt immer im Format der Variable.
Z.B.:
1
uint8_t i = -1;
ergibt 255.

./. schrieb:
> Das trifft im weiteren auch auf Konstanten in Berechnungen zu.

Genau das sagte ich bereits.


Peter

von Rolf Magnus (Gast)


Lesenswert?

./. schrieb:
> float pi;
>
> pi=3.1415926F;

Welchen Unterschied erwartest du bei dieser Anweisung ohne das F?

> grad=3.1415926F*rad/4.0F;

Hier wird es ja wieder als Teil einer Berechnung verwendet und nicht 
einfach nur zugewiesen.

von ./. (Gast)


Lesenswert?

>> float pi;
>>
>> pi=3.1415926F;

Das ist mir mal bei Renesas-Compilern aufgefallen.

Ohne das F an den Konstanten landeten immer Teie der recht umfänglichen
double-Library im viel zu kleinen Flash.
Und ich brauchte nur float.

Ob das jetzt standardkonform ist, will ich gar nicht beurteilen.

von Karl H. (kbuchegg)


Lesenswert?

./. schrieb:
>>> float pi;
>>>
>>> pi=3.1415926F;
>
> Das ist mir mal bei Renesas-Compilern aufgefallen.
>
> Ohne das F an den Konstanten landeten immer Teie der recht umfänglichen
> double-Library im viel zu kleinen Flash.
> Und ich brauchte nur float.
>
> Ob das jetzt standardkonform ist, will ich gar nicht beurteilen.

Dazu hat der Standard nichts zu sagen.
Der Standard kümmert sich nicht um Implementierungen sondern beschreibt 
das geforderte Verhalten eines Programms.
Nach Ausführung der Zeilen muss der Wert in der Variablen stehen. Wie er 
dorthin gelangt ist Sache des Compilers.

von (prx) A. K. (prx)


Lesenswert?

./. schrieb:

> Ohne das F an den Konstanten landeten immer Teie der recht umfänglichen
> double-Library im viel zu kleinen Flash.
> Und ich brauchte nur float.

Das kann passieren, muss aber nicht. Abgesehen von Berechnung von 
konstanten Ausdrücken in einem Kontext, der eine Konstante erwartet, 
sind Optimierungen im Standard nicht vorgeschrieben.

Allerdings hätte man schon erwarten können, dass die konstante 
Umrechnung einer double Konstanten in eine float Konstante vom Compiler 
übernommen und nicht erst zu Laufzeit durchgeführt wird.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Die Suffix spielt auch bei folgendem Code eine Rolle, Seispiel avr-gcc:
1
void a ();
2
3
void f (void)
4
{
5
    asm volatile ("" :: "r" (0l));
6
    a (-1l);
7
}

Ohne die Suffix werden 16-Bit Werte bereitgestellt, Mit dem L werden es 
32-Bit Werte.

A. K. schrieb:

> Allerdings hätte man schon erwarten können, dass die konstante
> Umrechnung einer double Konstanten in eine float Konstante vom
> Compiler übernommen und nicht erst zu Laufzeit durchgeführt wird.

Das setzt u.U eine komplette float- und double Emulation im Compiler 
voraus, da er sicherstellen muss, daß sich zB das Rundungsverhalten 
nicht durch die Optimierung ändert.

Selbst wenn auf dem Host-Rechner, auf dem der Compiler läuft, eine FPU 
vorhanden ist, kann diese nur dann verwendet werden, wenn die Ergebnisse 
genauso sind wie auf dem Target. Das genaue Verhalten kann etwa vom 
jeweiligen float-Layout abhängen.

Dies ist auch der Grund dafür, daß GCC Bibliotheken wie GMP und MPFR 
verwendet und voraussetzt: GCC verwendet nicht die Host-FPU für solche 
Compilezeit-Optimierungen, sondern macht alle Berechnungen per Emulation 
und mit dem Layout wie auf dem Target.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.