Forum: Mikrocontroller und Digitale Elektronik welche Datentypen soll ich nehmen?


von Felix (Gast)


Lesenswert?

Hallo,

ich habe angefangen mit einem AVR (Atmega328 auf einem Arduino Board) zu 
programmieren.

ich habe mal eine generelle frage zu den Datentypen.

wenn ich stdint.h includiere, kann ich uint8_t, uint16_t usw. benutzen.
Ansonsten muss ich bei den default typen bleiben unsigned char, unsigned 
int usw.

Welche Datentypen sollte ich der richtigkeithalber nehmen?

Und wieso nimmt man obwohl man stdint.h includiert hat für strings 
trotzdem noch char?

von Franko P. (sgssn)


Lesenswert?

Hi
die Variable vom Typ "int" ist der Urgrossvater der variablen in der 
Sprache C. Als das entwickelt wurde haben sich die Väter der Sprache 
keine grossen Gedanken gemacht, was denn das jetzt ist. Denn es ist 
klar, dass int der Registergröße des Prozessors entspricht. Dummerweise 
kam nach den 8-Bit Prozessoren die 16-Bitter, dann die 32.Bitter, also 
was ist int? Um da jetzt Klarheit reinzubringen wurden diese Variablen 
wie uint8_t oder uint16_t definiert um ganz klar darustellen was das 
jetzt für ne Variable sein soll. Und das ist jetzt portabel, also 
unabhängig vom Prozessor.
Und string gibts eigentlich in C nicht. Ein string ist daher ein Array 
aus Charactern, also "char" oder char[]


Gruß
Gerhard

: Bearbeitet durch User
von Einer K. (Gast)


Lesenswert?

Felix schrieb:
> Welche Datentypen sollte ich der richtigkeithalber nehmen?

Die Datentypen welche sich bei deinem Problem sinnvoll einsetzen lassen!
Da das Problem geheim ist, ist mir leider keine konkretere Antwort 
möglich.

Felix schrieb:
> Und wieso nimmt man obwohl man stdint.h includiert hat für strings
> trotzdem noch char?
Weil Strings eben aus Buchstaben bestehen....
Würde ich mal sagen....

von Felix (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> Weil Strings eben aus Buchstaben bestehen....
> Würde ich mal sagen....

Das ja, aber wenn ich nach dieser Tabelle gehe 
https://de.wikibooks.org/wiki/C-Programmierung_mit_AVR-GCC/_Datentypen, 
dann ist char mit uint8_t ersetzt worden.

Das beides das selbe ist, ist mir bewusst. Aber es ist mir trotzdem 
schon öfters aufgefallen, dass für variablen uint8_t, uint16_t usw. 
genommen worden ist und für Arrays aus charachtern (danke Franko P.) 
eben char.

Franko P. schrieb:
> Um da jetzt Klarheit reinzubringen wurden diese Variablen
> wie uint8_t oder uint16_t definiert um ganz klar darustellen was das
> jetzt für ne Variable sein soll. Und das ist jetzt portabel, also
> unabhängig vom Prozessor.

Dann werde ich auch alles in uint8_t uint16_t usw. machen

von Ingo E. (ogni42)


Lesenswert?

> Das beides das selbe ist, ist mir bewusst.
Dann ist Dir etwas Falsches bewusst.

char hat minimal 8bit; ein int8_t hat genau 8bit ("exact width 
type")

Details findest Du z.B. hier: https://en.wikipedia.org/wiki/C_data_types

von Kevin M. (arduinolover)


Lesenswert?

Der superior Dateityp ist eindeutig double.

von MaWin (Gast)


Lesenswert?

Ingo E. schrieb:
> char hat minimal 8bit; ein int8_t hat genau 8bit ("exact width
> type")

außerdem ist char entweder signed oder unsigned, je nach 
Compiler(-optionen).
int8_t hingegen ist immer signed.

von Einer K. (Gast)


Lesenswert?

Felix schrieb:
> dann ist char mit uint8_t ersetzt worden.

Nein, das ist keine Ersetzung!
uint8_t gibt es nur auf Prozessoren, welche auch wirklich 8Bit Grüppchen 
verarbeiten können.

uint8_t ist also ein optionaler Datentype.
char gibts auf JEDEM Kessel

von Thomas Z. (usbman)


Lesenswert?

Für 8 Bit CPUs gilt
- so kurz wie möglich uint8_t ist efektiver als uint16_t
- vorzeichenlose Typen verwenden wo möglich
- long nur dort wo das notwendig ist

beim ARM ist das allerdings nicht mehr so. Ich habe aber gemerkt, dass 
auch dort in vielen Fällen mit kurzen Variablen kompakterer Code 
generiert wird.

von Kevin M. (arduinolover)


Lesenswert?

Thomas Z. schrieb:
> beim ARM ist das allerdings nicht mehr so. Ich habe aber gemerkt, dass
> auch dort in vielen Fällen mit kurzen Variablen kompakterer Code
> generiert wird.

Zauberei?

von Axel S. (a-za-z0-9)


Lesenswert?

Felix schrieb:
> wenn ich stdint.h includiere, kann ich uint8_t, uint16_t usw. benutzen.
> Ansonsten muss ich bei den default typen bleiben unsigned char, unsigned
> int usw.

So weit, so offensichtlich.

> Welche Datentypen sollte ich der richtigkeithalber nehmen?

Den passenden! Und wenn das Programm nicht länger als nötig werden soll; 
den kürzesten passenden.

> Und wieso nimmt man obwohl man stdint.h includiert hat für strings
> trotzdem noch char?

Wieso "trotzdem"? Wenn man ein char haben will, nimmt man ein char. 
Wieso sollte das davon abhängen, welche Typen es außerdem noch gibt?

von Dirk B. (dirkb2)


Lesenswert?

Erinnere dich dann auch daran, dass es noch fastest und least Typen 
gibt, die oft angebrachter sind.

uint8_least_t funktioniert auch, wenn CHARBITS größer als 8 ist.

Die int8_t Familie nimmt man zum rechnen, Sie sind ein Ersatz für signed 
char und unsigned char.
Für char ist vom Standard nicht vorgegeben, ob es signed oder unsigned 
ist.
Zudem sind alle Stringfunktionen der Standard Library auf char 
ausgelegt.

von A. S. (Gast)


Lesenswert?

Franko P. schrieb:
> Dummerweise kam nach den 8-Bit Prozessoren die 16-Bitter, dann die
> 32.Bitter,
 8 Bitter kamen erst später für C. Die Väter haben sich große Gedanken 
gemacht, die vielen verschiedenen Architekturen zu unterstützen. Darum 
gab es 3 char, und 6 int.

Erst später haben sich Architekturen mit 8*2^n Bit durchgesetzt. Und nur 
deshalb konnte man konkret werden,

> wurden
> diese Variablen wie uint8_t oder uint16_t definiert um ganz klar
> darustellen was das jetzt für ne Variable sein soll


Verwendung:  char für Strings.

Ob Du nun unsigned char oder uint8_t für Zahlen verwendest, ist eher 
Geschmacksache. Der Vorteil von (u)int8_t: es knallt wenn der Prozessor 
keinen 8bit-typen hat. Bei unsigned char wirst Du höchstwahrscheinlich 
auf die Schnauze fallen, weil Du erwartest, dass nach 255 0 kommt.

Bei 16- oder 32 bit-Zahlen hilft ein uintXX_t nur bedingt. Spätestens 
bei Vergleichen, Rechnungen oder printf musst Du wissen, wie groß int 
ist oder größeren Aufwand treiben.

Beispiele: (keine Zeile funktioniert wie erwartet)

uint16_t u=10000*8/4;

uint16_t u2=100; int16 i=-1; if(i<u2) ...

printf("%u, %u", u, u2);

von Rolf M. (rmagnus)


Lesenswert?

Felix schrieb:
> Und wieso nimmt man obwohl man stdint.h includiert hat für strings
> trotzdem noch char?

Weil char der Typ für Text ist.

Franko P. schrieb:
> Hi
> die Variable vom Typ "int" ist der Urgrossvater der variablen in der
> Sprache C. Als das entwickelt wurde haben sich die Väter der Sprache
> keine grossen Gedanken gemacht, was denn das jetzt ist. Denn es ist
> klar, dass int der Registergröße des Prozessors entspricht. Dummerweise
> kam nach den 8-Bit Prozessoren die 16-Bitter, dann die 32.Bitter, also
> was ist int? Um da jetzt Klarheit reinzubringen wurden diese Variablen
> wie uint8_t oder uint16_t definiert um ganz klar darustellen was das
> jetzt für ne Variable sein soll. Und das ist jetzt portabel, also
> unabhängig vom Prozessor.

Ziemlich genau falsch herum. Die ursprünglichen Typen wurden gerade aus 
Gründen der Portabilität nicht auf bestimmte Größen festgezurrt. Damit 
lässt sich ein effizienter C-Compiler für fast jede erdenkliche 
Plattform erzeugen, was auch geschehen ist. Da man manchmal aber feste 
Größen braucht (oft in dem Teil des Code, der prozessorspezifisch und 
damit sowieso unportabel ist), wurden entsprechende typedefs mit C99 
nachgerüstet.

MaWin schrieb:
> Ingo E. schrieb:
>> char hat minimal 8bit; ein int8_t hat genau 8bit ("exact width
>> type")
>
> außerdem ist char entweder signed oder unsigned, je nach
> Compiler(-optionen).
> int8_t hingegen ist immer signed.

char sollte man auch nie für was anderes als für Text einsetzen. Da man 
damit nicht rechnet, spielt auch die Signedness keine Rolle.

Kevin M. schrieb:
> Thomas Z. schrieb:
>> beim ARM ist das allerdings nicht mehr so. Ich habe aber gemerkt, dass
>> auch dort in vielen Fällen mit kurzen Variablen kompakterer Code
>> generiert wird.
>
> Zauberei?

Vermutlich. Ich hätte erwartet, dass der Code etwas größer wird.

A. S. schrieb:
> Spätestens bei Vergleichen, Rechnungen oder printf musst Du wissen, wie
> groß int ist oder größeren Aufwand treiben.

Du musst wissen, welchen Zahlenraum der Typ mindestens können muss, 
damit die Rechnung hinein passt.

A. S. schrieb:
> uint16_t u2=100; int16 i=-1; if(i<u2) ...

Signed und unsigned zu mischen, ist sowieso oft keine gute Idee.

von Johann Klammer (Gast)


Lesenswert?

Typischerweise faengt man mit int an (fuer zahlen, counter etc)
und erst wenn einem das Ram ausgeht, probiert man halt arrays etc auf 
int8_t
uint8_t etc umzustellen.

Fuer strings eigentlich immer char*. Da ist es ganz egal ob stdint 
inkludiert ist oder nicht. Solange du nicht herumkonvertierst isses auch 
egal ob der jetzt signed oder unsigned ist.

Das unsigned ist wohl wegen der funktionen im ctype.h
<https://stackoverflow.com/questions/17975913/does-ctype-h-still-require-unsigned-char>;

(ist halt die Frage ob du die verwenden willst.)

von M. Н. (Gast)


Lesenswert?

Felix schrieb:
> Welche Datentypen sollte ich der richtigkeithalber nehmen?

Das bleibt dir überlasssen. Ich verwende auf embedded Code fast 
ausschließlich die stdint Typen, da ich dadurch die Bit-Breite der 
Variablen absolut im Griff habe und nicht jedes mal nachdenken muss, wie 
groß ein "in"t oder ein "long int" etc. auf der aktuellen Architektur 
sind.

Man kann sich bspw nur auf gewisse Randbedingungen verlassen:
1) ein char ist immer "1" groß. => sizeof(char) ist immer 1!
2) ein Integer "int" ist mindestens 16 bit breit. (AVR: 16 bit, x86: 32 
bit)
3) etc...

Felix schrieb:
> Und wieso nimmt man obwohl man stdint.h includiert hat für strings
> trotzdem noch char?

Weil ein "char" der Korrekte Typ für einen Buchstaben (Text) ist. Das 
ist wie oben geschrieben auch safe, da ein char nach C-Standard immer 1 
groß ist.

Das Hauptproblem an der Sache ist, dass C nicht spezifiziert, wie groß 
die anderen Typen sind, bzw. eben nur in auslegbaren Grenzen.

von (prx) A. K. (prx)


Lesenswert?

Für Text im Speicher in struct oder array ist "char" sinnvoll. Für einen 
Buchstaben in einer lokalen Variablen gilt das nicht zwangsläufig auch. 
Besonders nicht bei Nettigkeiten wie getchar():
    char c;
    while ((c = getchar()) != EOF)
       ...
geht garantiert irgendwie in die Hose.

: Bearbeitet durch User
von A. S. (Gast)


Lesenswert?

Rolf M. schrieb:
> Du musst wissen, welchen Zahlenraum der Typ mindestens können muss,
> damit die Rechnung hinein passt.

Das ist ja durch intxx_t in den Beispielen klar. Trotzdem hängt das 
Verhalten von der Größe von int ab.

von Axel S. (a-za-z0-9)


Lesenswert?

(prx) A. K. schrieb:
> Für Text im Speicher in struct oder array ist "char" sinnvoll. Für einen
> Buchstaben in einer lokalen Variablen gilt das nicht zwangsläufig auch.

Für ein Zeichen schon.

> Besonders nicht bei Nettigkeiten wie getchar():
>     char c;
>     while ((c = getchar()) != EOF)
>        ...
> geht garantiert irgendwie in die Hose.

Klar. Der Returntyp von getchar() ist ja auch nicht char.
Auch wenn der Name ("get char") das suggeriert.

von (prx) A. K. (prx)


Lesenswert?

Axel S. schrieb:
>> Für Text im Speicher in struct oder array ist "char" sinnvoll. Für einen
>> Buchstaben in einer lokalen Variablen gilt das nicht zwangsläufig auch.
>
> Für ein Zeichen schon.

Bedingt. Wenn (u)int_fast8_t nicht mit (u)int8_t identisch ist, ist char 
wahrscheinlich weniger effizient als int.

: Bearbeitet durch User
von A. S. (Gast)


Lesenswert?

Axel S. schrieb:
> Klar. Der Returntyp von getchar() ist ja auch nicht char.

Zeichen sind meist int in c. Nur in Strings oder %c sind es char.

von Dirk B. (dirkb2)


Lesenswert?

Axel S. schrieb:
> Klar. Der Returntyp von getchar() ist ja auch nicht char.
> Auch wenn der Name ("get char") das suggeriert.

Der Name passt schon, es wird ja ein char vom Stream gelesen.

von Axel S. (a-za-z0-9)


Lesenswert?

Dirk B. schrieb:
> Axel S. schrieb:
>> Klar. Der Returntyp von getchar() ist ja auch nicht char.
>> Auch wenn der Name ("get char") das suggeriert.
>
> Der Name passt schon, es wird ja ein char vom Stream gelesen.

Jein. Aus dem Stream wird schon ein Zeichen gelesen. Das wird 
allerdings nicht direkt zurück gegeben, weil man dann ja EOF oder einen 
Fehler nicht in den Returnwert tun könnte (inband signaling).

Also wird das gelesene unsigned(!) char in ein signed(!) int konvertiert 
und bei Bedarf eine magische Konstante (eben EOF) zurück gegeben. Ja, 
der eigentliche Fehler ist das inband signaling.

von Rolf M. (rmagnus)


Lesenswert?

A. S. schrieb:
> Rolf M. schrieb:
>> Du musst wissen, welchen Zahlenraum der Typ mindestens können muss,
>> damit die Rechnung hinein passt.
>
> Das ist ja durch intxx_t in den Beispielen klar.

Damit ist es aber meistens überspezifiziert. Wichtig ist, dass deine 
Werte rein passen, d.h. eine Mindestgröße reicht. Eine exakte Größe ist 
da nicht nötig. Für das beste Optimierungspotenzial ist es sinnvoll, 
möglichst genau zu nennen, was man braucht und nur das. Das lässt dem 
Compiler mehr Freiheiten.

> Trotzdem hängt das Verhalten von der Größe von int ab.

Klar, man muss die Mindestgrößen eben kennen und darf für portablen Code 
int nicht für Werte größer 32767 verwenden. Aber man hat ja alle 
Möglichkeiten, den Typ passend zu wählen. Entweder nimmt man int und 
akzeptiert das, weiß aber auch, dass es 32 oder gar 64 Bit groß sein 
kann. Oder man nimmt, wenn es auf Platzsparen kommt, dafür int_least16_t 
und bekommt das kleinstmögliche, was diesen Wertebereich kann, und das 
sogar auf Prozessoren, die gar keinen exakt 16 Bit großen Typen kennen. 
Oder man nimmt int_fast16_t, wenn es einem eher auf Geschwindigkeit 
ankommt. Dann kann der Compiler notfalls auch einen größeren Typen 
nehmen, wenn der auf diesem Prozessor effizienter ist.
Mit einem int16_t geht nichts davon, ohne dass man dafür irgendeinen 
echten Vorteil hätte. Die einzige Ausnahme sind die Stellen, für die die 
exakte Größe wirklich zwingend erforderlich ist, wie z.B. Zugriff auf 
Hardware-Register.

Axel S. schrieb:
> Klar. Der Returntyp von getchar() ist ja auch nicht char.
> Auch wenn der Name ("get char") das suggeriert.

Tatsächlich verwendet C für einzelne Zeichen durchgängig int als Typ. So 
wird z.B. auch bei Funktionen toupper() oder isalpha() immer int 
verwendet. Nur bei Arrays/Pointern darauf wird char verwendet.

von Dirk B. (dirkb2)


Lesenswert?

Axel S. schrieb:
> Also wird das gelesene unsigned(!) char in ein signed(!) int konvertiert

Dabei geht aber nicht die Information über das Zeichen verloren. Das 
passiert dummerweise erst, wenn es wieder in einem signed char abgelegt 
wird. Dann erst hat es möglicherweise einen negativen Wert.

Aber eigentlich ging es um den Namen

von Klaus W. (mfgkw)


Lesenswert?

A. S. schrieb:
> Zeichen sind meist int in c. Nur in Strings oder %c sind es char.

Etwas genauer zu %c: da geht es nur bei den scanf-Funktionen um char 
bzw. char*.
Bei den printf-Funktionen wird tatsächlich eine int übergeben (von der 
dann aber nur das unterste char ausgegeben wird).

A. S. schrieb:
> Bei 16- oder 32 bit-Zahlen hilft ein uintXX_t nur bedingt. Spätestens
> bei Vergleichen, Rechnungen oder printf musst Du wissen, wie groß int
> ist oder größeren Aufwand treiben.
>
> Beispiele: (keine Zeile funktioniert wie erwartet)
>
> uint16_t u=10000*8/4;
>
> uint16_t u2=100; int16 i=-1; if(i<u2) ...
>
> printf("%u, %u", u, u2);

Kommt drauf an, woher deine Erwartungen kommen :-)

Wenn man die Formatkürzel aus inttypes.h nimmt für die (u)intxx_t 
anstatt der falschen Formatbeschreibungen wie in deinem Beispiel, 
entspricht es zumindest meinen Erwartungen.

von A. S. (Gast)


Lesenswert?

Rolf M. schrieb:
>> Trotzdem hängt das Verhalten von der Größe von int ab.
>
> Klar, man muss die Mindestgrößen eben kennen und darf für portablen Code
> int nicht für Werte größer 32767 verwenden.

Du hast die Beispiele anscheinend nicht verstanden: Es geht nicht um den 
Wertebereich der Variablen (der ist fest), sondern dass (obwohl int 
nirgendwo auftaucht) das Verhalten auf Plattformen mit 16 Bit ein völlig 
anderes ist als auf Plattformen mit 32 Bit.

von A. S. (Gast)


Lesenswert?

Klaus W. schrieb:
> Kommt drauf an, woher deine Erwartungen kommen :-)

Wie Du an anderen Kommentaren siehst, sind sich einige der 
unterschiedlichen  Verhalten nicht mal bewusst. Da ist ganz leichtfertig 
ein "immer uintxx_t" gefällt und plötzlich sieht man sich damit 
konfrontiert, sämtliche Formatspecifier umzubauen. Noch schlimmer, von 
reinen Strings im Quelltext auf weniger flüssige Konstrukte zu wechseln 
(die ggf. auch schwieriger zu finden sind)

> Wenn man die Formatkürzel aus inttypes.h nimmt für die (u)intxx_t
> anstatt der falschen Formatbeschreibungen wie in deinem Beispiel,
> entspricht es zumindest meinen Erwartungen.
Aber nur das printf. Die beiden anderen Zeilen hängen noch immer von der 
int-Größe ab.

Ja, es gibt für alles Workarounds, klar. Doch irgendwo in der Matrix 
[Warnungen/Casts/implizite Typumwandlungen/..] hakt es dann doch, wenn 
ich den gleichen Code auf 16- und 32-Bit-Plattformen laufen lassen will. 
Und das ist z.B. immer dort der Fall, wo der 16er-µC-Code auch am PC 
läuft.

Beitrag #6734804 wurde von einem Moderator gelöscht.
von Rolf M. (rmagnus)


Lesenswert?

A. S. schrieb:
> Rolf M. schrieb:
>>> Trotzdem hängt das Verhalten von der Größe von int ab.
>>
>> Klar, man muss die Mindestgrößen eben kennen und darf für portablen Code
>> int nicht für Werte größer 32767 verwenden.
>
> Du hast die Beispiele anscheinend nicht verstanden:

Ich glaube, ich habe missverstanden, was du damit sagen wolltest.

> Es geht nicht um den Wertebereich der Variablen (der ist fest), sondern
> dass (obwohl int nirgendwo auftaucht) das Verhalten auf Plattformen mit
> 16 Bit ein völlig anderes ist als auf Plattformen mit 32 Bit.

int taucht quasi implizit in den Integer-Literalen auf. Dazu kommt dann 
noch ggf. Integer-Promotion.

von A. S. (Gast)


Lesenswert?

Rolf M. schrieb:
> int taucht quasi implizit in den Integer-Literalen auf.

OK, dann ausführlich:

A. S. schrieb:
> uint16_t u=10000*8/4;
Nur bei 32 Bit 40.000, sonst etwa 3600

> uint16_t u2=100; int16 i=-1; if(i<u2) ...
Bei 32 Bit erfüllt, bei 16 nicht.

> printf("%u, %u", u, u2);
Bei beiden OK ;-)

von Rolf M. (rmagnus)


Lesenswert?

A. S. schrieb:
> Rolf M. schrieb:
>> int taucht quasi implizit in den Integer-Literalen auf.
>
> OK, dann ausführlich:

Warum?

> A. S. schrieb:
>> uint16_t u=10000*8/4;
> Nur bei 32 Bit 40.000,

Ich hätte ja 20.000 erwartet…

> sonst etwa 3600

Ja natürlich, weil 10000, 8 und 4 vom Typ int sind. Wie ich sagte: int 
taucht in den Literals implizit auf.

>> uint16_t u2=100; int16 i=-1; if(i<u2) ...
> Bei 32 Bit erfüllt, bei 16 nicht.

Ja, auch wie ich sagte: Integer Promotion.

>> printf("%u, %u", u, u2);
> Bei beiden OK ;-)

Ja, ist in dem Fall ok, weil unsigned int nicht kleiner sein kann als 
uint16_t, und wenn es größer sein sollte, macht das auf Grund der 
Integer Promotion nichts. Es kann aber trotzdem zu einer 
Compiler-Warnung führen.

von Einer K. (Gast)


Lesenswert?

A. S. schrieb:
> int16
Interessanter Datentype!


Dann, selbst wenn man _t dran hängt meldet mein Kompiler:
1
E:\Programme\arduino\portable\sketchbook\sketch_jun22b\sketch_jun22b.ino: In function 'void setup()':
2
E:\Programme\arduino\portable\sketchbook\sketch_jun22b\sketch_jun22b.ino:11:7: warning: comparison of integer expressions of different signedness: 'int16_t' {aka 'int'} and 'uint16_t' {aka 'unsigned int'} [-Wsign-compare]
3
   11 |   if(i<u2)cout << F("och")<< endl;
4
      |      ~^~~
Wenigstens "Verdächtig" ist es also schon, was du da zutun versuchst.


Zum anderen Punkt
1
E:\Programme\arduino\portable\sketchbook\sketch_jun22b\sketch_jun22b.ino:13:19: warning: integer overflow in expression of type 'int' results in '14464' [-Woverflow]
2
   13 |   uint16_t u=10000*8/4;
3
      |              ~~~~~^~
Auch da kommt der "Rechenfehler" nicht ganz überraschend, würde ich mal 
sagen!

uint16_t u=10000UL*8/4;
Das UL kostet einen doch nichts.....
Und "Zack" läuft es auf jedem Kesselchen, wie man sich das wünscht.
(wenn denn uint16_t darauf funktioniert)

von A. S. (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> Auch da kommt der "Rechenfehler" nicht ganz überraschend, würde ich mal
> sagen!

Ja klar geht das alles. Hab ich ja auch geschrieben. Und auch auf jene 
eingegrenzt, die nur wenige Warnungen haben, z.B. -Wall.

Die Diskussionen sind nur dann immer die gleichen, wenn es nicht geht: 
"Ne, auf dem PC gehts ja, ... und der Wertebereich ist auch nicht 
überschritten mit 40.000, ... Der Compiler ist kaputt, der kann kein *".

von Einer K. (Gast)


Lesenswert?

A. S. schrieb:
> Die Diskussionen sind nur dann immer die gleichen, wenn es nicht geht:
> "Ne, auf dem PC gehts ja, ... und der Wertebereich ist auch nicht
> überschritten mit 40.000, ... Der Compiler ist kaputt, der kann kein *".

Naja...
Das ist ja auch kein Wunder....

Da muss(te) jeder mal durch!
Nicht einer für alle, sondern jeder einzeln für sich.

Beim ersten mal, wird einem danach klar, dass es doch Sinn macht, die 
Sprachreferenz mal durchzublättern.
Beim zweiten mal kann man sich in den Arsch beißen.
Und bei jedem weiteren mal, sieht man es schon beim schreiben.

Der ganz normale Weg...
Einige der "Erstlinge" tauchen hier auf, was dann scheinbar zu "immer 
die gleichen Diskussionen" führt. Sind es aber nicht, da sich die 
Individuen unterscheiden.

von Mampf F. (mampf) Benutzerseite


Lesenswert?

Felix schrieb:
> char mit uint8_t ersetzt worden.

oft ist char aber auch int8_t^^ kann man als compiler-schalter angeben, 
was char sein soll :)

von Klaus W. (mfgkw)


Lesenswert?

nein, weder noch.

Mit dem Compilerschalter meinst du die Entscheidung, ob char als signed 
char oder als unsigned char betrachtet werden soll.

(u)int8_t hat damit nicht mehr zu tun, als daß bei den allermeisten 
Systemen die Länge gleich ist wie bei den char-Typen.

von (prx) A. K. (prx)


Lesenswert?

Wobei man die Feinheit beachten sollte, dass char zwar entweder das 
Vorzeichenverhalten von signed char oder von unsigned char hat, aber
  char
  unsigned char
  signed char
im Typsystem trotzdem drei formal verschiedene Typen darstellen.

Da (u)int8_t in stdint.h üblicherweise auf (un)signed char abgebildet 
wird, ist char also mit beiden nicht identisch.

In C wird man oft erst dann damit konfrontiert, wenn man die Warnungen 
einschaltet und dann vielleicht darauf hingewiesen wird, dass die 
"signedness" sich von beiden Alternativen unterscheidet.

In C++ spielt das eine grössere Rolle.

: Bearbeitet durch User
von Einer K. (Gast)


Lesenswert?

(prx) A. K. schrieb:
> In C++ spielt das eine grössere Rolle, weil man sonst bei Templates
> schnell gegen die Wand läuft.

Da gibts dann aber ein  is_signed<char>::value oder auch 
is_signed<char>() für.
Und seine ganzen Brüder.

von Rolf M. (rmagnus)


Lesenswert?

(prx) A. K. schrieb:
> In C++ spielt das eine grössere Rolle, weil man sonst bei Templates
> schnell gegen die Wand läuft.

Oder bei überladenen Funktionen. So bricht der Compiler hier z.B. mit 
Fehler ab, weil er nicht weiß, welche Überladung er nehmen soll:
1
void foo(signed char)
2
{
3
}
4
5
void foo(unsigned char)
6
{
7
}
8
9
int main()
10
{
11
    char c = 5;
12
    foo(c);
13
}

Andererseits erstaunt dann, dass bei einem:
1
    char c = 65;
2
    signed char d = 66;
3
    unsigned char e = 67;
4
    uint8_t f = 68;
5
    int8_t g = 69;
6
    
7
    std::cout << c << d << e << f << g << '\n';
sämtliche Variablen als Text ausgegeben werden. Man könnte eigentlich 
erwarten, dass die Überladungen des <<-Operators so definiert sind, dass 
nur die erste als Text ausgegeben wird.

von (prx) A. K. (prx)


Lesenswert?

Arduino Fanboy D. schrieb:
>> In C++ spielt das eine grössere Rolle, weil man sonst bei Templates
>> schnell gegen die Wand läuft.
>
> Da gibts dann aber ein  is_signed<char>::value oder auch
> is_signed<char>() für.
> Und seine ganzen Brüder.

Es geht freilich nicht nur um das Vorzeichen. Sondern auch um Overloads 
und Templates, in denen das drei verschiedene Typen sind. Und man das 
nicht vergessen darf.

An dieser Stelle wird es bei C vs C++ noch etwas tricky. Denn in C ist 
in alter Tradition 'x' vom Typ int - nicht etwa char, wie man annehmen 
sollte. Hätte man das in C++ genauso durchgezogen, wären
   char c;
   f(c);    // f(char)
   f('x');  // C: f(int), C++: f(char)
verschiedene Fälle. Weshalb in C++ 'x' vom Typ char ist.

von Einer K. (Gast)


Lesenswert?

Rolf M. schrieb:
> Man könnte eigentlich
> erwarten, dass die Überladungen des <<-Operators so definiert sind, dass
> nur die erste als Text ausgegeben wird.

Dann macht Arduino ja doch mal was richtig..... ;-)
1
#include <Streaming.h> // die Lib findest du selber 
2
Stream &cout {Serial}; 
3
4
void setup() 
5
{
6
  Serial.begin(9600);
7
  cout << F("Start: ") << F(__FILE__) << endl;
8
9
  char c = 65;
10
  signed char d = 66;
11
  unsigned char e = 67;
12
  uint8_t f = 68;
13
  int8_t g = 69;
14
    
15
    cout << c << d << e << f << g << '\n';
16
}
17
18
void loop() 
19
{
20
21
}

Ausgabe:
1
Start: E:\Programme\arduino\portable\sketchbook\sketch_jun22c\sketch_jun22c.ino
2
A66676869

von A. S. (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> Da muss(te) jeder mal durch!
> Nicht einer für alle, sondern jeder einzeln für sich.

+1

Man hat hier sonst immer den Eindruck, dass sowas gar nicht passieren 
darf und man vor seinem ersten "Hallo Welt" doch zumindest (alles) 
verstanden haben sollte.

von Experte (Gast)


Lesenswert?

Thomas Z. schrieb:
> - vorzeichenlose Typen verwenden wo möglich

Das ist unnötig gefährlich.

Sehr viele Berechnungen kommen teilweise in's Negative. Der Klassiker 
sind rückwärts zählende Schleifen.

Vorzeichenlose Typen produzieren gerne schwer zu findende Bugs.


> Für 8 Bit CPUs gilt
> - so kurz wie möglich uint8_t ist efektiver als uint16_t

In den allermeisten Fällen ist das eine unnötige Einschränkung. Auf 
8-Bit-CPUs ist bei den meisten Operationen nur ein einfacher Takt und 
Opcode mehr notwendig, für eine 16-Bit-Operation.

Ein int16_t sollte auf 8-Bit-CPUs der "Standard-Datentyp" sein. Damit 
kann man in der Regel den kompletten RAM addresieren. Die Größe passt 
gut zu einem 8-Bit-System.

von (prx) A. K. (prx)


Lesenswert?

Experte schrieb:
> 8-Bit-CPUs ist bei den meisten Operationen nur ein einfacher Takt und
> Opcode mehr notwendig, für eine 16-Bit-Operation.

CPUs mit einem einzigen 8-Bit Akkumulator magst du nicht 
berücksichtigen?

: Bearbeitet durch User
von Tilo R. (joey5337) Benutzerseite


Lesenswert?

Kevin M. schrieb:
> Thomas Z. schrieb:
>> beim ARM ist das allerdings nicht mehr so. Ich habe aber gemerkt, dass
>> auch dort in vielen Fällen mit kurzen Variablen kompakterer Code
>> generiert wird.
>
> Zauberei?

Vorab: genau wissen tu ich es nicht.

Aber: ich gehe davon aus, dass bei kleinerem Wertebereich der Compiler 
mehr Tricks anwenden kann.
D.h. selbst wenn der Compiler ein int16_t und ein int32_t auf ARM beide 
in 4 Byte speichert, weil es eine 32-bit-Architektur ist, so kann er 
später doch zusätzliche Optimierungen anwenden. Weil der Compiler genau 
weiß, dass dein Programm niemals etwas größeres als 32767 speichern 
wird. (Das wäre nämlich undefined behaviour und der Compiler wäre dann 
frei, irgendwas beliebiges zu machen)

Das klingt jetzt vielleicht unglaubwürdig, weil, welche Tricks sollen 
das sein, wenn mir keiner einfällt?

Hier empfehle ich folgendes Video:
https://www.youtube.com/watch?v=w0sz5WbS5AM

Das ist die Keynote der Konferenz C++ on Sea 2019:
What Everyone Should Know About How Amazing Compilers Are - Matt Godbolt

von Sebastian (Gast)


Lesenswert?

Vorsicht bei char für Textzeichen! Da passt nur eine minimale Untermenge 
von Codepoints rein!

:)

LG, Sebastian

von (prx) A. K. (prx)


Lesenswert?

Tilo R. schrieb:
> Aber: ich gehe davon aus, dass bei kleinerem Wertebereich der Compiler
> mehr Tricks anwenden kann.

Es gibt beide Effekte.

Der andere zeigt sich bei:
 uint16_t x;
 x++;
denn ARMs haben keine 16-Bit Register.

Fall 1: x besteht ausschliesslich aus den unteren 16 Bits, die oberen 
sind undefiniert. Dann kann man mit dem Register oft nicht rechnen.

Fall 2: x ist mit Nullen erweitert im Register gespeichert. Dann muss 
man den Überlauf aus den unteren 16 Bits berücksichtigen, der ggf den 
zulässigen Wertebereich verletzt. Also hinterher maskieren.

Auch aufgrund solcher Sauereien gibt es uint_fast16_t.

Und zur Optimierung solcher Sauereien ist Überlauf mit Vorzeichen 
undefiniert. Rechne damit, dass dann ein Wert in x stehen kann, der da 
eigentlich nicht reinpasst - statt dem intuitiv angenommenen 
Modulo-Verhalten. Das erspart die Reduktion auf 16-Bits, mit 
Nebeneffekt.

: Bearbeitet durch User
von A. S. (Gast)


Lesenswert?

Experte schrieb:
> Sehr viele Berechnungen kommen teilweise in's Negative. Der Klassiker
> sind rückwärts zählende Schleifen.
>
> Vorzeichenlose Typen produzieren gerne schwer zu findende Bugs.

In der Regel ist es genau umgekehrt. Signed-Typen dürfen NICHT 
überlaufen. Man muss z.B. vor einem ++ abfragen, ob das passiert. Bei 
einer Rechnung sowieso.

Konstrukte wie "while(u-->=0)" sind keine Bugs sondern Arroganz des 
Programmierers der alle Warnungen in den Wind schlägt.

Ich habe nichts gegen Schleifenzähler int i. Aber meist reicht unsigned 
und ist auch einfach sauberer. Z.B. weil bei Arrays die Prüfung auf 
beide Grenzen wegfällt (i>=0 && i<N). Weil der %-Operator über den 
ganzen Wertebereich gleichmäßig funktioniert. Weil ein Überlauf 
definiertes Verhalten hat. Weil eine Division durch 2^n durch >> ersetzt 
werden kann, ...

von (prx) A. K. (prx)


Lesenswert?

A. S. schrieb:
> In der Regel ist es genau umgekehrt.

Andererseits muss der Compiler bei Rechnung ohne Vorzeichen u.U. das 
definierte Modulo-Verhalten berücksichtigen. Mit Vorzeichen kann er auf 
Teufel komm raus rechnen wie seine Bits gewachsen sind.

Ist nicht so einfach zu erkennen, was summarum besser ist.

: Bearbeitet durch User
von A. S. (Gast)


Lesenswert?

(prx) A. K. schrieb:
> Mit Vorzeichen kann er auf
> Teufel komm raus rechnen wie seine Bits gewachsen sind.

Du meinst, weil Überträge dann UB sind? Dass solltest Du aber mit einem 
Ironietag versehen.

von (prx) A. K. (prx)


Lesenswert?

A. S. schrieb:
> Du meinst, weil Überträge dann UB sind? Dass solltest Du aber mit einem
> Ironietag versehen.

Das war ausnahmsweise nicht wirklich ironisch. UB bei Rechnung mit 
Vorzeichen ist ein grösseres Risiko für unvorsichtige Programmierer, 
kann aber effizienteren Code zur Folge haben.

von Tilo R. (joey5337) Benutzerseite


Lesenswert?

A. S. schrieb:
> In der Regel ist es genau umgekehrt. Signed-Typen dürfen NICHT
> überlaufen. Man muss z.B. vor einem ++ abfragen, ob das passiert. Bei
> einer Rechnung sowieso.
>
> Konstrukte wie "while(u-->=0)" sind keine Bugs sondern Arroganz des
> Programmierers der alle Warnungen in den Wind schlägt.

Vollkommen korrekt!
Mein Beispiel war mit Bedacht gewählt. Der Signed-Überlauf ist undefined 
behaviour (im Gegensatz zu unsigned, wo das definiert ist). Es ist pures 
Glück, dass es so oft funktioniert.

Und wenn man ganz großes Pech hat, ist jahrelang funktionierender Code 
plötzlich kaputt, weil der neue Compiler eine zulässige, 
standardkonforme Optimierung macht.

von Experte (Gast)


Lesenswert?

Es ist ziemlich praxisfern zu argumentieren, dass unbeabsichtigte 
Überläufe mit undefinierten Verhalten schlimmer wären als 
unbeabsichtigte Überläufe mit definierten Verhalten.

In den allermeisten Fällen ist ein Überlauf in einem Programm eine 
Katastrophe.

Und genau hier liegt der Knackpunkt bei unsigned: Man arbeitet ständig 
an der "Klippe", der Null rum. Wenn durch einen Rundungsfehler in 
Berechnungen eine Ergebnis "etwas" unter Null rutscht, ist bei unsigned 
die Katastrophe vorprogrammiert, und bei signed oft folgenlos.

A. S. schrieb:
> Konstrukte wie "while(u-->=0)" sind keine Bugs sondern Arroganz des
> Programmierers der alle Warnungen in den Wind schlägt.

Wenn 'u' ein vorzeichenbehafteter Datentyp ist, wirst Du niemals eine 
Compilerwarnung bekommen, weil einfach nichts passieren kann. Das ist 
deutlich robuster als einen Datentyp zu verwenden, der potentiell 
gefährlich ist.

Zusätzlich ist bei vorzeichenlosen Datentypen die mentale Belastung von 
Programmierern höher, da sie sicherstellen müssen, niemals in den 
negativen Bereich zu rutschen.

Jede "räumliche Nähe" von vorzeichenlosen Datentypen und Minuszeichen 
ist eine scharfe Tretmine, die nur darauf wartet, ausgelöst zu werden.

Fehler in Software haben einen "Hard-Fact" als letzten Auslöser, z.B. 
einen Unterlauf einer vorzeichenlose Variable, aber der Weg dorthin wird 
geebnet durch unzählige "Soft-Facts", und dazu gehört die Verwendung 
eben solcher Datentypen die solche Unterläufe (in der Nähe der Null) 
erst ermöglichen.

von Einer K. (Gast)


Lesenswert?

Experte schrieb:
> Es ist ziemlich praxisfern zu argumentieren, dass unbeabsichtigte
> Überläufe mit undefinierten Verhalten schlimmer wären als
> unbeabsichtigte Überläufe mit definierten Verhalten.

Ganz im Gegenteil!
Ein definiertes Verhalten ist eine absolute Notwendigkeit.
Das undefinierte Verhalten, ist da eher auf dem Niveau einer tödlichen 
Falle.

von Dirk B. (dirkb2)


Lesenswert?

Experte schrieb:
> Wenn 'u' ein vorzeichenbehafteter Datentyp ist, wirst Du niemals eine
> Compilerwarnung bekommen,

Da kommt sowas wie „Ausruck ist immer wahr“

von Rolf M. (rmagnus)


Lesenswert?

Arduino Fanboy D. schrieb:
> Experte schrieb:
>> Es ist ziemlich praxisfern zu argumentieren, dass unbeabsichtigte
>> Überläufe mit undefinierten Verhalten schlimmer wären als
>> unbeabsichtigte Überläufe mit definierten Verhalten.
>
> Ganz im Gegenteil!
> Ein definiertes Verhalten ist eine absolute Notwendigkeit.

In beiden Fällen baut dein Programm Mist. Im zweiten ist es immerhin 
definierter Mist.

von Der Robs (Gast)


Lesenswert?

Experte schrieb:
> In den allermeisten Fällen ist das eine unnötige Einschränkung. Auf
> 8-Bit-CPUs ist bei den meisten Operationen nur ein einfacher Takt und
> Opcode mehr notwendig, für eine 16-Bit-Operation.
>
> Ein int16_t sollte auf 8-Bit-CPUs der "Standard-Datentyp" sein. Damit
> kann man in der Regel den kompletten RAM addresieren. Die Größe passt
> gut zu einem 8-Bit-System.

NACK.

Sorry, aber wer nicht im Griff hat, welche Werte seine Daten annehmen 
können, kann auch mit int16_t Probleme bekommen. Es ist nur weniger 
Wahrscheinlich.

Es gilt: Als Datentype bevorzugt die "Datengröße" des µC, uint8_t bei 
8bit, uint16_t bei 16bit etc. Datentype ansolsten so groß wie nötig, so 
klein wie möglich.

Gruß

Robert

von Oliver S. (oliverso)


Lesenswert?

Der Robs schrieb:
> Datentype ansolsten so groß wie nötig, so
> klein wie möglich.

Oder so schnell wie möglich, oder so aligned wie möglich, oder...

Pauschalaussagen sind da nutzlos.

Oliver

von Cyblord -. (cyblord)


Lesenswert?

"Programmieren ist wie Romane schreiben: Erst denkt man sich ein paar 
Typen aus und dann muss man sehen wie man mit ihnen zurecht kommt."

von Rolf M. (rmagnus)


Lesenswert?

Oliver S. schrieb:
> Der Robs schrieb:
>> Datentype ansolsten so groß wie nötig, so klein wie möglich.
>
> Oder so schnell wie möglich, oder so aligned wie möglich, oder...

Und idealerweise sich automatisch an die konkrete Plattform anpassend, 
wo es keinen konkreten Grund gibt, warum das an der Stelle nicht geht.

> Pauschalaussagen sind da nutzlos.

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.