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?
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
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....
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
> 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
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.
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
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.
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?
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?
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.
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);
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.
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.)
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.
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
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.
(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.
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
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.
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.
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.
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.
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
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.
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.
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.
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.
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 ;-)
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.
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)
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 *".
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.
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 :)
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.
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
(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.
(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.
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.
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
|
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.
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.
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
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
Vorsicht bei char für Textzeichen! Da passt nur eine minimale Untermenge von Codepoints rein! :) LG, Sebastian
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
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, ...
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
(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.
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.
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.
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.
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.
Experte schrieb: > Wenn 'u' ein vorzeichenbehafteter Datentyp ist, wirst Du niemals eine > Compilerwarnung bekommen, Da kommt sowas wie „Ausruck ist immer wahr“
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.
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
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
"Programmieren ist wie Romane schreiben: Erst denkt man sich ein paar Typen aus und dann muss man sehen wie man mit ihnen zurecht kommt."
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.