Forum: Mikrocontroller und Digitale Elektronik uint64_t maximaler Wertebereich nicht nutzbar


von Veit D. (devil-elec)


Lesenswert?

Hallo,

folgendes lässt sich auf einem 8Bit AVR nicht kompilieren.
1
uint64_t var {18'446'744'073'709'551'615};
error: integer constant is so large that it is unsigned [-Werror]

Habe verschiedene avr-gcc Toolchains ausprobiert. Kürze ich den Wert um 
eine Stelle klappt es. Kennt jemand den Grund für den gekürzten 
Wertebereich?

von Cyblord -. (cyblord)


Lesenswert?

Schon mal mit UL am Ende versucht? Sonst wird das Literal als signed 
interpretiert.

von Rolf M. (rmagnus)


Lesenswert?

Cyblord -. schrieb:
> Schon mal mit UL am Ende versucht?

Das ist eigentlich für ein unsigned long, kein uint64_t. Es müsste so 
heißen:
1
uint64_t var { UINT64_C(18'446'744'073'709'551'615)};

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Hallo,
1
uint64_t var {18'446'744'073'709'551'615ULL};
2
uint64_t var { UINT64_C(18'446'744'073'709'551'615) };
Es funktioniert mit beiden Varianten. Danke Euch.
Mich wundert das jetzt nur ein wenig. Ich war bis jetzt der Meinung das 
man Präfixe wie 'UL' nur beim rechnen mit direkt eingesetzten Literalen 
benötigt. Weil sonst in Standard 'int' gerechnet wird. Hier steht 
allerdings der Datentyp mit uint64_t fest.

Hier ist 'var' auch 32 Bit ohne zusätzliches Präfix.
1
uint32_t var {1};

Grob probiert, dass Problem taucht ab ca. 9E+18 auf.
Wenn ich mir das genauer anschaue liegt das auf der 63Bit Grenze.
1
 int64_t var1 {9'223'372'036'854'775'807}; // ok
2
uint64_t var2 {9'223'372'036'854'775'807}; // ok
3
uint64_t var3 {9'223'372'036'854'775'808}; // nicht ok, zu groß

Mit größer dem halben Wertebereich gibts ein Problem. Ist das normales 
Verhalten? Muss man sich das einfach merken das uint64_t eine 
Sonderbehandlung benötigt, also als Gegeben hinnehmen oder gibts dafür 
erklärbare Gründe? Weil die Sonderbehandlung ist ab 19 Digits notwendig.

von EAF (Gast)


Lesenswert?

Veit D. schrieb:
> Präfixe
Suffix

Veit D. schrieb:
> Hier steht allerdings der Datentyp mit uint64_t fest.
Das mag ja sein....
Nur ist ein dezimales numerisches Literal per Default signed
> (no suffix)
> int
> long int
> long long int (since C++11)
Aus: https://en.cppreference.com/w/cpp/language/integer_literal

von Veit D. (devil-elec)


Lesenswert?

Hallo,

okay "Suffix".  ;-)

signed (bzw. default int) ist ein Literal wenn man es ohne Datentyp 
verwendet.

Das hier rechnet ohne Suffix wie erwartet richtig.
1
uint32_t a {4'000'000'000};
2
uint32_t b {1};
3
uint32_t c = a-b;

Hierfür benötigt man ein Suffix, weil sonst alles als int behandelt 
wird, bevor das Ergebnis c zugewiesen wird.
1
uint32_t c = 9UL*11000;

Mit Datentyp braucht man kein Suffix.
1
uint32_t a {11000};
2
uint32_t c = 9*a;

von mIstA (Gast)


Lesenswert?

Veit D. schrieb:
> Hier ist 'var' auch 32 Bit ohne zusätzliches Präfix.
> uint32_t var {1};

Hier wird eine Konstante (1) vom Typ int zur Initialisierung von var 
verwendet; ein positiver int Wert kann problemlos nach uint32_t 
konvertiert werden.


Veit D. schrieb:
> Wenn ich mir das genauer anschaue liegt das auf der 63Bit Grenze.

Klar, weil sobald diese Grenze überschritten wird, wird der Wert der 
Konstanten zu groß für den Wertebereich von long long int, dem größten 
vorzeichenbehafteten Ganzzahlen-Typ, weshalb der Compiler mit dieser 
Ziffernfolge nichts mehr anfangen kann.

von EAF (Gast)


Lesenswert?

Bist du denn jetzt glücklich?
Oder gibt es noch weitere Klarheiten, welche man beseitigen sollte?

von Rolf M. (rmagnus)


Lesenswert?

Veit D. schrieb:
> Hier steht allerdings der Datentyp mit uint64_t fest.

Für den Typ spielt es keine Rolle, was du nachher damit machst. Das 
uint64_t deiner Variable ist für den Typ des Literals nicht von 
Bedeutung.

Veit D. schrieb:
> Hier ist 'var' auch 32 Bit ohne zusätzliches Präfix.
> uint32_t var {1};

var ja, aber 1 nicht. 1 ist vom Typ int. Nun passt 1 in den Wertebereich 
von int, daher gibt es hier kein Problem. Der größtmögliche Typ für ein 
Integer-Literal ohne Suffix ist long long, und da passt 
9'223'372'036'854'775'808 nicht rein. In beiden Fällen hat das mit dem 
Typ der Variablen gar nichts zu tun.

von Johannes S. (Gast)


Lesenswert?

es gibt doch auch den Suffix LL bzw. ULL, damit sollte es doch auch 
gehen?
1
    uint64_t var {18'446'744'073'709'551'615ULL};
2
    printf("Hello World %lu", var);

funktioniert im online gdb in C++.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

okay, habs wohl fast verstanden oder doch noch nicht.

Das heißt am funktionierenden Bsp. ohne Suffix uint32_t var 
{4'000'000'000} wird das erst nach long long konvertiert, weil es in ein 
long nicht reinpasst und danach nochmal nach unsigned long konvertiert? 
Komischerweise kann er hier jedenfalls nach unsigned ohne Suffix 
konvertieren. Oder anders gefragt, in welcher Wandlungsreihenfolge wird 
hier 4'000'000'000 nach uint32_t konvertiert? Weil wenn er es hier 
gleich von int nach uint32_t konvertiert, dann könnte er auch gleich von 
int nach uint64_t konvertieren. Versteht jemand mein Gedankenproblem?

@ Johannes S.
'ULL' wurde gleich zu Anfang mit getestet und funktioniert.

von Oliver S. (oliverso)


Lesenswert?

Veit D. schrieb:
> Versteht jemand mein Gedankenproblem?

Bestimmt. Daher hat mal irgend jemand das im Standard festgeschrieben.

Und google weiß das...
z.B.

https://en.cppreference.com/w/cpp/language/integer_literal

Damit ist auch klar, warum da ein 'u' als suffix ausreicht.

Oliver

von Dirk B. (dirkb2)


Lesenswert?

Johannes S. schrieb:
> uint64_t var {18'446'744'073'709'551'615ULL};
>     printf("Hello World %lu", var);

Bist du dir sicher, dass %lu zu uint64_t  passt?
Nicht nur auf deinem System.

von Johannes S. (Gast)


Lesenswert?

Dirk B. schrieb:
> Bist du dir sicher, dass %lu zu uint64_t  passt?

ich bin sicher das es auf 'meinen' Cortex-M nicht passt, da möchte der 
gcc ein %llu haben. Ich weiß auch das es dafür Macros gibt die das 
systemabhängig richtig machen, aber die machen das deutlich 
unleserlicher und die verwende ich nicht gerne.
Es ging aber auch nicht um das printf, das war nur Q&D um Test. Und im 
Online GDB gehts eben mit %lu.

von EAF (Gast)


Lesenswert?

Veit D. schrieb:
> ohne Suffix uint32_t var
> {4'000'000'000} wird das erst nach long long konvertiert,
Nein, da wird nichts "erst" konvertiert zumindest nicht, wenn du einen 
impliziten Cast meinst. Es ist der Parser/Compiler, welcher dort das 
Literal in 64 Bit erzeugt.
Erst die Initialisierung erfordert den implizite Cast nach unsigned long

Tipp:
Beschäftige dich mit TypeTraits.
using Type = decltype(4'000'000'000);
Dann kann man damit den Type ausführlich untersuchen.


PS:
TypeTraits werden beim AVR Gcc nicht mitgeliefert.
Gibt aber alternative Implementierungen.


Veit D. schrieb:
> Versteht jemand mein Gedankenproblem?
Nein, ich nicht.
Nicht das Problem, was du mit den Typen hast.

Aber das, welches bei dir die Erkenntnis behindert, das glaube ich zu 
kennen. Natürlich aus eigener Erfahrung.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Veit D. schrieb:
> Das heißt am funktionierenden Bsp. ohne Suffix uint32_t var
> {4'000'000'000} wird das erst nach long long konvertiert,

Nein, es wird nicht nach long long konvertiert, es ist bereits vom Typ
long long. Der Grund dafür ist, dass

> es in ein long nicht reinpasst

Sonst wäre es vom Typ long oder int.

> und danach nochmal nach unsigned long konvertiert?

Es wird konvertiert, aber nicht "nochmal", da das die erste und einzige
Konvertierung ist, die hier stattfindet.

> Komischerweise kann er hier jedenfalls nach unsigned ohne Suffix
> konvertieren.

Signed kann immer nach unsigned konvertiert werden, nicht nur hier.

Dein ursprüngliches Problem hat mit Konvertierungen überhaupt nichts zu
tun, sondern damit, dass das Signed-Literal 18'446'744'073'709'551'615
außerhalb des vom GCC unterstützten Wertebereichs (-9223372036854775808
bis 9223372036854775807) liegt. Mit dem U-Suffix wird daraus ein
Unsigned-Literal, dessen Wertebereich von 0 bis 18446744073709551615
reicht.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich glaube wir kommen der Sache näher. Ich dachte bis gestern ich hätte 
das mit impliziten cast und wann ein Suffix notwendig ist verstanden. 
Aber hier mit dem höchsten Wertebereich komme ich nicht klar warum sich 
der Compiler dabei blöd anstellt.

Wenn hier
1
uint16_t var {65'000}
2
uint32_t var {4'000'000'000}
automatisch der Richtige implizite cast gemacht wird.

Warum dann nicht auch hier ?
1
uint64_t var {18'446'744'073'709'551'615}

Ich meine der Compiler weiß doch das der Wert nicht in ein signed passt.
Genauso wie er es bei 4 Milliarden auch weiß. Hier macht er dann 
demzufolge den richtigen impliziten cast nach uint32_t. Wenn der gcc 
uint64_t nicht kennen würde könnte er ja mit dem Suffix ULL nichts 
anfangen.

Aktuell würde ich mir merken das man auch bei einer 
Variableninitialisierung mit Wertzuweisung größer int64_t ein Suffix 
benötigt. Nur das warum ist trotz aller euren Erklärungen nicht zu 100% 
klar.

von EAF (Gast)


Lesenswert?

Veit D. schrieb:
> der Compiler dabei blöd anstellt
Es ist nicht der Compiler, welcher sich blöd anstellt.

Es ist die Sprachdefinition, welche das nicht hergibt.
Dein 18'446'744'073'709'551'615 passt einfach nicht in int64_t rein.
Und darum hauts dir das um die Ohren.

Du projizierst deine Wünsche auf den Compiler.
Da ist der Haken.
Er ist kein "Wünsch dir was"

Zu den Casts:
Mittlerweile warnen die Compiler bei (fast) jeder Verlust behafteten 
impliziten Konvertierung. Das ist eine Gnade der Compiler(ersteller), 
keine Pflicht.
Genau so ist es keine Pflicht, dass du die Sprachdefinition akzeptierst.
Aber, es ist ein Kampf gegen Windmühlen, wenn du dagegen anstrampelst.

Der Compiler bietet dir die Möglichkeit an jedes Literal, welches du 
gerne als unsigned haben möchtest, auch ein U dran zu hängen.

Beitrag #6837591 wurde vom Autor gelöscht.
von Veit D. (devil-elec)


Lesenswert?

Hallo,

wenn man die Aussage so allein betrachtet ist alles okay.
Wegen der Overflow Warnung. Deswegen schreibe ich die geschweiften 
Klammern.
Nur warum funktioniert dann hierbei
1
uint32_t var {4'000'000'000}
der implizite cast? Warum ist hier keine Suffix Unterstützung notwendig? 
Das sollte eigentlich laut den Erklärungen genauso wenig funktionieren. 
Tut es aber.

Wenn alle Literale oberhalb von 'int' ein Suffix benötigen würden wäre 
es mir verständlicher, weil int der Standarddatentyp ist. Aber nun gibts 
dummerweise ein funktionierendes uin32_t Bsp. was die Logik 
durcheinander bringt. Das bringt mich außer Tritt.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Vergiss erst einmal sämtliche Casts und implizite Konvertierungen und
betrachte stattsessen das folgende, auf das Wesentliche reduzierte
Beispiel:
1
int main() {
2
  18'446'744'073'709'551'615;
3
}

Auch hier erhältst du die Warnung "integer constant is so large that it
is unsigned".

Nun schau in der folgende Tabelle nach, welche Datentypen für ein
Integer-Literal in Dezimalschreibweise in Frage kommen:

  https://en.cppreference.com/w/cpp/language/integer_literal#The_type_of_the_literal

Es sind dies
- int
- long int
- long long int

In welchen dieser Datentypen passt die große Zahl?

Richtig: In keinen davon. Deswegen ist die riesige Zahl ohne einen
U-Suffix achlicht und ergreifend ein Fehler.

Mit einem U-Suffix sind die in Frage kommenden Datentypen lt. der
verlinkten Tabelle
- unsigned int
- unsigned long int
- unsigned long long int

Der letzte davon ist ausreichend groß, um die Zahl aufzunehmen, deswegen
gibt es hier keine Warnung.

Machst du die Zahl aber noch größer, so erhältst du erneut eine Warnung,
nämlich "integer constant is too large for its type".

: Bearbeitet durch Moderator
von EAF (Gast)


Lesenswert?

Veit D. schrieb:
> der implizite cast? Warum ist hier keine Suffix Unterstützung notwendig?
> Das sollte eigentlich laut den Erklärungen genauso wenig funktionieren.
> Tut es aber

Du irrst!
{4'000'000'000} wird vom Compiler zu einem long long als int64_t.
Der Wert lässt sich verlustfrei nach uint32_t konvertieren.
Also auch keine Warnung.
Warum auch?
Ist doch alles gut.
1
  using TypeA = decltype(18'446'744'073'709'551'615);
2
3
  cout << F("is_signed<TypeA>(): ")        << is_signed<TypeA>()        << endl;
4
  cout << F("is_same<TypeA,uint64_t>(): ") << is_same<TypeA,uint64_t>() << endl;
5
  
6
  using TypeB = decltype(18'446'744'073'709'551'615u);
7
8
  cout << F("is_signed<TypeB>(): ")        << is_signed<TypeB>()        << endl;
9
  cout << F("is_same<TypeB,uint64_t>(): ") << is_same<TypeB,uint64_t>() << endl;
10
  
11
  using TypeC = decltype(4'000'000'000);
12
13
  cout << F("is_signed<TypeC>(): ")        << is_signed<TypeC>()        << endl;
14
  cout << F("is_same<TypeC,int64_t>(): ")  << is_same<TypeC,int64_t>()  << endl;
15
  cout << F("is_same<TypeC,int32_t>(): ")  << is_same<TypeC,int32_t>()  << endl;
16
  cout << F("is_same<TypeC,uint64_t>(): ") << is_same<TypeC,uint64_t>() << endl;
17
  cout << F("is_same<TypeC,uint32_t>(): ") << is_same<TypeC,uint32_t>() << endl;

von Cyblord -. (cyblord)


Lesenswert?

Also kurz gefasst:

Man kann auf das Suffix verzichten wenn es einen größeren Datentyp gibt, 
welcher die Zahl als signed aufnehmen kann.

: Bearbeitet durch User
von EAF (Gast)


Lesenswert?

Ach ja, die Ausgabe habe ich vergessen:
1
is_signed<TypeA>(): 1
2
is_same<TypeA,uint64_t>(): 0
3
is_signed<TypeB>(): 0
4
is_same<TypeB,uint64_t>(): 1
5
is_signed<TypeC>(): 1
6
is_same<TypeC,int64_t>(): 1
7
is_same<TypeC,int32_t>(): 0
8
is_same<TypeC,uint64_t>(): 0
9
is_same<TypeC,uint32_t>(): 0

von EAF (Gast)


Lesenswert?

Cyblord -. schrieb:
> Man kann auf das Suffix verzichten wenn es einen größeren Datentyp gibt,
> welcher die Zahl als signed aufnehmen kann.
Nicht ganz korrekt...

Besser:
Ohne Präfix und ohne Suffix ist das numerische Literal immer signed.

Denn mit 0x Präfix gilt die Einschränkung nicht.

von Cyblord -. (cyblord)


Lesenswert?

EAF schrieb:
> Cyblord -. schrieb:
>> Man kann auf das Suffix verzichten wenn es einen größeren Datentyp gibt,
>> welcher die Zahl als signed aufnehmen kann.
> Nicht ganz korrekt...
>
> Besser:
> Ohne Präfix und ohne Suffix ist das numerische Literal immer signed.

Nur erklärt dies das Problem noch nicht. Das Problem ist dass es keinen 
Datentyp gibt, welcher diese signed Zahl aufnehmen kann. Genau dieses 
Detail fehlt hier einigen zum Verständnis. Dass die Zahl ohne Suffix 
(und ohne Präfix) als signed interpretiert wird, scheint allgemein klar 
zu sein.

: Bearbeitet durch User
von EAF (Gast)


Lesenswert?

Cyblord -. schrieb:
> scheint allgemein klar zu sein.
Naja...

Dann müssten wir nicht darüber reden... nehme ich mal an.

Cyblord -. schrieb:
> interpretiert
Das ist vielleicht das richtige Wort!

Der Compiler interpretiert den Quellcode.
Und erst danach, bei der Initialisierung der Variablen erfolgt der 
implizite Cast.
Das sind 2 völlig unterschiedliche Vorgänge, die nix miteinander gemein 
haben, außer, dass sie hier zufällig in eine Zeile stehen.

Für beide Vorgänge stehen die Regeln, nach denen das geschieht, im 
Handbuch.

von Cyblord -. (cyblord)


Lesenswert?

EAF schrieb:
> Dann müssten wir nicht darüber reden... nehme ich mal an.

Nochmal:
Den meisten war klar dass die Typen ohne Suffix signed sind.
Denen war aber nicht klar, dass die Probleme daher kamen dass kein 
nächstgrößerer Datentyp mehr für diesen signed Wert verfügbar war.

So interpretiere ich jedenfalls die Posts von Veit D.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Johannes S. schrieb:
> Dirk B. schrieb:
>> Bist du dir sicher, dass %lu zu uint64_t  passt?
>
> ich bin sicher das es auf 'meinen' Cortex-M nicht passt, da möchte der
> gcc ein %llu haben. Ich weiß auch das es dafür Macros gibt die das
> systemabhängig richtig machen, aber die machen das deutlich
> unleserlicher und die verwende ich nicht gerne.

Die sind aber die einzige Möglichkeit, das universell richtig zu machen.

Veit D. schrieb:
> ich glaube wir kommen der Sache näher. Ich dachte bis gestern ich hätte
> das mit impliziten cast und wann ein Suffix notwendig ist verstanden.

Es gibt keine impliziten Casts, sondern nur implizite Konvertierungen.

> Aber hier mit dem höchsten Wertebereich komme ich nicht klar warum sich
> der Compiler dabei blöd anstellt.

Integer-Literals, die in dezimal und ohne Suffix angegeben sind, können 
einen von genau drei Typen annehmen. Default ist int. Wenn das zu klein 
ist, wird long genommen, und wenn das auch zu klein ist, wird long long 
genommen. Und wenn das zu klein ist, gibt es einen Fehler. Alle anderen 
Typen sind dafür verboten.

> Wenn hier
> uint16_t var {65'000}
> uint32_t var {4'000'000'000}
> automatisch der Richtige implizite cast gemacht wird.

65'000 ist auf einem AVR vom Typ long, 4'000'000'000 vom Typ long long. 
Mit der Konvertierung nach uint16_t bzw. uint32_t hat das immer noch 
überhaupt nichts zu tun. Nochmal: Für den Typ des literal spielt der Typ 
der Variablen, die damit in diesem Fall initialisiert wird, absolut 
keine Rolle.

> Warum dann nicht auch hier ?uint64_t var {18'446'744'073'709'551'615}

Weil 18'446'744'073'709'551'615 schon falsch ist. Es ist zu groß für 
long long, also kommt ein Fehler. Auch hier hat das rein gar nichts 
damit zu tun, dass das dann in eine Variable vom Typ uint64_t 
geschrieben wird.

> Ich meine der Compiler weiß doch das der Wert nicht in ein signed passt.

Er hat aber vom Standard die Vorgabe, dass es signed sein muss.

> Genauso wie er es bei 4 Milliarden auch weiß.

Hier reicht aber der Wertebereich von long long aus, um die Zahl 
darzustellen.

> Hier macht er dann demzufolge den richtigen impliziten cast nach
> uint32_t.

Das kommt nach der Stelle mit dem Problem.

> Aktuell würde ich mir merken das man auch bei einer
> Variableninitialisierung mit Wertzuweisung größer int64_t ein Suffix
> benötigt.

… größer long long.

von EAF (Gast)


Lesenswert?

Cyblord -. schrieb:
> Denen war aber nicht klar,

Wenn ich den "Problemkomplex" analysiere, dann komme ich zu folgendem 
Schluss:
Bisher wurden Variablen schon fleißig mit numerischen Literalen 
initialisiert.
Eine Gewohnheit geworden.
Ist auch ok, völlig normal.

Plötzlich ein ungewohnter Wert, und eine Warnung.

Der Fehler war, nicht schon vorher mal ins Buch zu schauen, nach dem 
Motto "Was passiert da wirklich?", sondern sich lieber in der Fantasie 
einen Vorgang ausmahlen.

Der konkrete Fall macht dann die Diskrepanz zwischen Fantasie und 
Realität zum Brennpunkt.
Natürlich wird dann erstmal überall der Übeltäter gesucht. Die eigenen 
Vorstellungen/Fantasien, werden eigentlich nicht so gerne auf den 
Prüfstand gestellt oder gar als irrig anerkannt. Sie müssten ja 
korrigiert werden.

Motto:
> irren ist menschlich
> im Irrtum verharren ist Dummheit

Das eigene Beharrungsvermögen steht da der Einsicht gerne quer im Weg 
rum.

Eigentlich wäre es klüger, sich vor der ersten Verwendung von Literalen 
kundig zu machen, so erst gar nicht diesen Irrweg beschreiten.
Aber, naja, wer ist schon immer uneingeschränkt klug?

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich denke ich habs verstanden. Ich danke Euch allen. Hier kamen 
verschiedene Dinge, auch Gewohnheiten, zusammen die am Ende zu einem 
großen Fragezeichen führten.  ;-)  Danke.

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.