Forum: Compiler & IDEs avr-gcc: Warnung bei fehlendem Cast aktivieren?


von Manfred (Gast)


Lesenswert?

Wenn ich sowas schreibe, kriege ich keine Warnung. Ist das so gewollt?
1
for (uint8_t i = 0; i < 32; i++) {
2
   uint32_t mask = 1 << i;
3
   // ...
4
}

Welcher Kommandozeilenparameter muss angegeben werden, damit eine 
Warnung erscheint?

von Name (Gast)


Lesenswert?

Anders gefragt. Warum erwartest du eine Warnung?

von Name (Gast)


Lesenswert?

Lass einmal die for weg. Erwartest du auch hierbei eine Warnung?
uint32_t var = 88

von Jemand (Gast)


Lesenswert?

Name schrieb:
> Anders gefragt. Warum erwartest du eine Warnung?

Ggf. ist int nur 16 Bit groß, dann wäre der Code so fehlerhaft.

von foobar (Gast)


Lesenswert?

Ist so gewollt, da mit -Wsign-conversion zu viele Meldungen kommen ;-)

von Rolf M. (rmagnus)


Lesenswert?

Name schrieb:
> Anders gefragt. Warum erwartest du eine Warnung?

Ich vermute, es geht darum, dass 1 und damit auch 1 << i vom Typ int 
ist. Man müsste eigentlich UINT32_C(1) << i schreiben. Bei einem 
32-Bit-int führt 1 << 31 zu undefiniertem Verhalten. Beim AVR ist int 
sogar nur 16 Bit breit.

: Bearbeitet durch User
von foobar (Gast)


Lesenswert?

> Bei einem 32-Bit-int führt 1 << 31 zu undefiniertem Verhalten.

Ist nicht sogar jeglicher left-shift eines signed Typen undefiniert? 
Eine gcc-Warn-Option dafür ist mir aber nicht bekannt (wäre wohl noch 
unpopulärer als -Wsign-conversion ;-) ).

von Rolf M. (rmagnus)


Lesenswert?

foobar schrieb:
>> Bei einem 32-Bit-int führt 1 << 31 zu undefiniertem Verhalten.
>
> Ist nicht sogar jeglicher left-shift eines signed Typen undefiniert?

Nein, nur wenn der zu schiebende Wert negativ ist oder das Ergebnis der 
entsprechenden Multiplikation mit 2ᶦ vom Typ nicht repräsentiert werden 
kann.

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

Jemand schrieb:
> Name schrieb:
>> Anders gefragt. Warum erwartest du eine Warnung?
>
> Ggf. ist int nur 16 Bit groß, dann wäre der Code so fehlerhaft.

Beim avr-gcc ist das so.

Oliver

: Bearbeitet durch User
von Manfred (Gast)


Lesenswert?

Ja, int ist 16 Bit breit, daher ist der Code falsch. Da muss es doch 
eine Warnung geben...

von test (Gast)


Lesenswert?

Manfred schrieb:
> Ja, int ist 16 Bit breit, daher ist der Code falsch. Da muss es
> doch eine Warnung geben...

Da müsste der gcc aber schon sehr clever sein um hier das Problem zu 
sehen. Kann man so etwas heutzutage erwarten?

Ist eigentlich Aufgabe des Programierers derartige Probleme zu erkennen. 
D.h. wenn man mit Bits rummacht sollte man generell wissen was für 
Datentypen man da benutzt.

von Bernd K. (prof7bit)


Lesenswert?

test schrieb:
> Kann man so etwas heutzutage erwarten?
>
> Ist eigentlich Aufgabe des Programierers derartige Probleme zu erkennen.

Darüber kann man gespaltener Meinung sein. Es gibt auch die Theorie 
derzufolge es das Ziel sein sollte eine Sprache (oder in Ermangelung 
einer neuen Sprache dann halt eben einen Compiler für eine existierende) 
so zu entwerfen daß der Programmierer bestimmte (möglichst viele) Arten 
von Fehlern schlichtweg einfach nicht mehr machen kann da man 
mittlerweile gelernt hat daß menschliche Programmierer zwar unglaublich 
clevere Algorithmen ersinnen können aber dennoch immer wieder so blöde 
kleine Flüchtigkeitsfehler machen.

Das resoniert auch gut mit der Utopie daß eigentlich die Maschinen dem 
Menschen dienen sollen und nicht umgekehrt.

: Bearbeitet durch User
von Manfred (Gast)


Lesenswert?

Schaut euch mal die ersten Kommentare an. Ich hatte meine Frage bewusst 
schwammig formuliert, um zu demonstrieren, wie leicht man hier den 
Fehler übersehen kann.
Der Compiler ist schon recht schlau. Wenn ich ein Array einbaue, mit 
Größe 31 und das in der Schleife befülle, kommt ja auch sofort eine 
Warnung.
1
uint8_t test[31];
2
for (uint8_t i = 0; i < 32; i++) {
3
   test[i] = 0;
4
   // ...
5
}

von Rolf M. (rmagnus)


Lesenswert?

test schrieb:
> Manfred schrieb:
>> Ja, int ist 16 Bit breit, daher ist der Code falsch. Da muss es
>> doch eine Warnung geben...
>
> Da müsste der gcc aber schon sehr clever sein um hier das Problem zu
> sehen.

Der Compiler müsste etwas Code-Analyse betreiben. Wenn man die Zeile
1
uint32_t mask = 1 << i;
für sich betrachtet, ist da erstmal nichts falsch dran. Eine 1 wird um 
eine variable Anzahl von Bits geschoben. Solange man den Wert von i 
nicht kennt, ist eine Warnung nicht gerechtfertigt. Jetzt ist es an der 
Stelle aber nicht übermäßig schwer für den Compiler, zu wissen, mit 
welchen Werten von i dieser Code definitiv ausgeführt werden wird. Der 
Optimizer analysiert solche Schleifen sowieso, um z.B. loop-unrolling 
betreiben zu können. Von daher könnte er hier schon merken, dass der 
Code definitiv fehlerhaft ist.

> Kann man so etwas heutzutage erwarten?
>
> Ist eigentlich Aufgabe des Programierers derartige Probleme zu erkennen.

Heutzutage ist es aber Aufgabe des Compilers, nicht nur das Programm 
einfach stur in Maschinencode zu übersetzen, wenn das irgendwie geht, 
sondern auch, dem Programmierer zu helfen, Fehler zu vermeiden.
Er übernimmt heute auch die Rolle eines Lint-Tools.

> D.h. wenn man mit Bits rummacht sollte man generell wissen was für
> Datentypen man da benutzt.

Das sollte man. Wie das aber so bei Menschen ist, macht man dabei auch 
schnell mal einen Flüchtigkeitsfehler.

von test (Gast)


Lesenswert?

Rolf M. schrieb:
> Das sollte man. Wie das aber so bei Menschen ist, macht man dabei auch
> schnell mal einen Flüchtigkeitsfehler.

Wobei die Tatsache das ein uint32_t offenbar nur 16bit hat (oder auch 
nicht, hängt bei c offenbar auch von der Mondphase und dem Wochentag ab) 
nicht wirklich hilfreich dabei ist den Überblick zu behalten ;-)


Wobei ich da vermutlich auch unfair gegenüber c++ bin, vermutlich gibt 
es geeignete Datentypen und Arten vernünftig zu programmieren. Macht nur 
keiner ;-) Ich meine wenn ich sehe das heutzutage immer noch Bytes in 
Char Arrays gesammelt werden...

von Wilhelm M. (wimalopaan)


Lesenswert?

test schrieb:
> Rolf M. schrieb:
>> Das sollte man. Wie das aber so bei Menschen ist, macht man dabei auch
>> schnell mal einen Flüchtigkeitsfehler.
>
> Wobei die Tatsache das ein uint32_t offenbar nur 16bit hat (oder auch
> nicht, hängt bei c offenbar auch von der Mondphase und dem Wochentag ab)
> nicht wirklich hilfreich dabei ist den Überblick zu behalten ;-)

Ein uint32_t hat auf jeder Plattform 32-Bit. Ein int/unsigned int hat 
mindestens 16-Bit.

von Rolf M. (rmagnus)


Lesenswert?

test schrieb:
> Wobei die Tatsache das ein uint32_t offenbar nur 16bit hat (oder auch
> nicht, hängt bei c offenbar auch von der Mondphase und dem Wochentag ab)
> nicht wirklich hilfreich dabei ist den Überblick zu behalten ;-)

Ich weiß nicht, wie du auf den Quatsch kommst. Das ist jedenfalls 
falsch.

> Wobei ich da vermutlich auch unfair gegenüber c++ bin, vermutlich gibt
> es geeignete Datentypen und Arten vernünftig zu programmieren. Macht nur
> keiner ;-) Ich meine wenn ich sehe das heutzutage immer noch Bytes in
> Char Arrays gesammelt werden...

Sollte unsigned char sein.

von Wilhelm M. (wimalopaan)


Lesenswert?

test schrieb:

> Wobei ich da vermutlich auch unfair gegenüber c++ bin, vermutlich gibt
> es geeignete Datentypen und Arten vernünftig zu programmieren. Macht nur
> keiner ;-) Ich meine wenn ich sehe das heutzutage immer noch Bytes in
> Char Arrays gesammelt werden...

Das machen nur C-Programmierer. In C++ nimmt man std::byte.

von MaWin (Gast)


Lesenswert?


von Bernd K. (prof7bit)


Lesenswert?

MaWin schrieb:
> Das könnte man wahrscheinlich mit ubsan finden.
> 
https://developers.redhat.com/blog/2014/10/16/gcc-undefined-behavior-sanitizer-ubsan/

Geht das mit bare-metal targets auch (denn im OP ist von avr-gcc die 
Rede)?

von MaWin (Gast)


Lesenswert?

Bernd K. schrieb:
> Geht das mit bare-metal targets auch (denn im OP ist von avr-gcc die
> Rede)?

Theoretisch schon.
Ich weiß nicht, ob avr-libc da Unterstützung hat (libubsan).
Vermutlich aber eher nicht.

von Wilhelm M. (wimalopaan)


Lesenswert?

Noch ein Antwort, weil es mir gerade über die Füße lief:

der UB-sanititer ist in C++ programmiert und benutzt die libstdc++. Da 
aber für das AVR target keine libstdc++ existiert, wird im configure des 
avr-gcc die lubsan deaktiviert.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Genau genommen gibt's die Libs schon; sowohl libstdc++-v3 als auch 
libsupc++, wobei letztere Teil der libstdc++-v3 ist und den rudimentären 
C++ Support bereitstellt wie z.B. new und new[].  Müsste halt mal jemand 
die handvoll Anpassungen für AVR machen.  Vorgesehen ist das, müsste nur 
noch ausgefüllt werden.

Die Sanitizer wollen aber noch mehr als libstdc++v3.  Aus 
libsanitizer/configure.ac:
1
# Common libraries that we need to link against for all sanitizer libs.
2
link_sanitizer_common='-lpthread -lm'
Will also auch POSIX.

von Christoph Z. (christophz)


Lesenswert?

test schrieb:
> vermutlich gibt es geeignete Datentypen und Arten vernünftig zu
> programmieren. Macht nur keiner ;-)
> Ich meine wenn ich sehe das heutzutage immer noch Bytes in
> Char Arrays gesammelt werden...

Ja, ist leider immer noch sehr verbreitet.

Seit C99 gibt es die stdint.h und stdbool.h (daraus kommt der schon 
erwähnte uint32_t). Damit ist es für einen Programmierer möglich seine 
Algorithmen so zu schreiben, dass sie Chance haben einen 
Plattformwechsel zu überleben und gibt auch den Tools mehr Chance für 
hilfreiche Warnungen.

Wird aus meiner Sicht zu wenig geschult bzw. ist zu selten Bestandteil 
der Firmen Coding-Guidelines.

test schrieb:
> Da müsste der gcc aber schon sehr clever sein um hier das Problem zu
> sehen. Kann man so etwas heutzutage erwarten?

Ja.

test schrieb:
> Ist eigentlich Aufgabe des Programierers derartige Probleme zu erkennen.
> D.h. wenn man mit Bits rummacht sollte man generell wissen was für
> Datentypen man da benutzt.

Ja. Darum sollten auch anständig definierte Datentypen (wie z. B. aus 
stdint.h) verwendet werden. So ist gleichzeitig auch der Code besser 
dokumentiert ohne Mehraufwand.

Rolf M. schrieb:
> Er übernimmt heute auch die Rolle eines Lint-Tools.

Ein heutiger Compiler soll heutzutage viele Prüfungen eines Linters 
(Statisches Codeanalyse Werkzeug) übernehmen, so weit wie er das halt 
kann. Zusätzlich kann der Compiler Dinge prüfen, die abhängig von der 
Zielplattform sind, was nicht in den Bereich eines Linters passt.

Da dazu noch keine Namen genannt wurden, hier zwei OpenSource Linter für 
C/C++:

http://splint.org/

http://cppcheck.sourceforge.net/

Spielt mal damit herum, ist sehr spannend was die Tools so finden 
können.
Hatte mir mal in einem Firmenprojekt (durfte ein Projekt von einem 
Kollegen übernehmen) massiv Zeit eingespart.

von Oliver S. (oliverso)


Lesenswert?

Christoph Z. schrieb:
> http://splint.org/

Splint Release 3.1.2
12 July 2007

Wer mit Compilern unterwegs ist oder sein muß, die auch aus dieser Zeit 
stammen, für den mag sowas hilfreich sein.

Aktuelle Compiler können bzgl. statischer Codeanalyse inzwischen sehr 
viel mehr.

Oliver

von Lotta  . (mercedes)


Lesenswert?

Wilhelm M. schrieb:

> Das machen nur C-Programmierer. In C++ nimmt man std::byte.
Das is ein Ding. ;-O

Was ist denn jetzt besser für Geschwindigkeit, Sicherheit
und Größe?

Bit Funktionen als Makro / Inline zu schreiben oder
die Byte-Klasse zu nehmen?


mfg

von Μαtthias W. (matthias) Benutzerseite


Lesenswert?

Oliver S. schrieb:
> Christoph Z. schrieb:
>> http://splint.org/
>
> Splint Release 3.1.2
> 12 July 2007
>
> Wer mit Compilern unterwegs ist oder sein muß, die auch aus dieser Zeit
> stammen, für den mag sowas hilfreich sein.
>
> Aktuelle Compiler können bzgl. statischer Codeanalyse inzwischen sehr
> viel mehr.

Ist zwar kein Compiler aber https://clang.llvm.org/extra/clang-tidy/ 
sollte auch nicht im eigenen Werkzeugkasten fehlen.

Matthias

von Oliver S. (oliverso)


Lesenswert?

Dazu alle verfügbaren sanitizer, dazu valgrind. Das sind dann zwar nicht 
nur statische, sondern auch run-time-Analysen, hilft aber trotzdem.

Oliver

von Μαtthias W. (matthias) Benutzerseite


Lesenswert?

Und verschiedene Compiler in möglichst aktueller Version. gcc und clang 
finden viel und viel unterschiedliches.

Und da wir hier bei µC.net sind: Die "Geschäftslogik" einer Anwendung 
sollte sich nicht nur für den µC übersetzen lassen. Denn nur so kann man 
die Laufzeitchecker (asan, ubsan, valgrind) richtig einsetzen.

Matthias

von Wilhelm M. (wimalopaan)


Lesenswert?

Der TO wollte sicher keine Laufzeitlösung mit sanitizern, etc., sondern 
eine Compilezeitlösung für sein Problem.

Abgesehen davon, dass es "richtiger" wäre zu schreiben
1
    for(uint8_t i{}; i < 32; i++) {
2
        uint32_t mask = uint32_t{1} << i;
3
    }

liegt die Wurzel des Übels aber an anderer Stelle:

- eine Ganzzahl vom Type uint32_t wird als Bitmaske missbraucht
- der Datentyp für den Bit-Shift (uint8_t) steht in keiner Relation zu 
dem Typ der Bitmaske

Eine simple Laufzeitlösung wäre korrekterweise folgendes:
1
    for(uint8_t i{}; i < 33; i++) {
2
        assert(i < (sizeof(uint32_t) * 8));
3
        uint32_t mask = uint32_t{1} << i;
4
    }

Das schreibt zwar so keiner hin, wäre aber der korrekte Ansatz, denn der 
gültige Wertebereich für den BitShift kann durch den Datentyp uint8_t 
nicht eingehalten werden. Also trägt der Programmierer die Verantwortung 
das zuzusichern: eine klassische Vorbedingung.

Für eine "richtige" Lösung braucht man

- einen (generischen) Datentyp (etwa: mask_t<>) für eine Bitmaske mit N 
Bits
- einen Shiftoperator für mask_t<>,
- einen ganzzahligen Datentyp, der nur den Wertebereich des Shifts der 
Maske abdeckt.

Damit hätten wir eine Lösung der Form:
1
    using m_t = mask_t<32>;
2
    for(m_t::shift_type i; i; ++i) {
3
        m_t mask = m_t{0x0001} << i;
4
    }

Die obigen Fehlerquellen sind ausgeschlossen.

Ist nicht schwer umzusetzen (std::byte ist da nur ein erster Schritt in 
diese Richtung), genauso wie etwa bounded_integer (ein ganzzahliger 
Datentyp mit beliebigen Grenzen und einstellbarer overrun-policy).

Das lässt sich mal wieder in das Thema strong types einordnen bzw. "Die 
eingebauten Datentypen sind dazu da, nicht benutzt zu werden".

von ppc (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Das lässt sich mal wieder in das Thema strong types einordnen bzw. "Die
> eingebauten Datentypen sind dazu da, nicht benutzt zu werden".

Genau so ist es.

von nur malso (Gast)


Lesenswert?

in diesem Falle hilft eine statische code analyse

wie z.B. cppcheck, hier eine onlinedemo 
http://cppcheck.sourceforge.net/demo/

von Wilhelm M. (wimalopaan)


Lesenswert?

nur malso schrieb:
> in diesem Falle hilft eine statische code analyse

cppcheck --enable=all findet das Problem nicht ...

von nur malso (Gast)


Lesenswert?

also bei mir führt (in der onlinedemo)
1
void f()
2
{
3
  for (uint8_t i = 0; i < 32; i++) {
4
    uint32_t mask = 1 << i;
5
  }
6
}

zu folgender Ausgabe
1
Cppcheck 1.86
2
3
[test.cpp:4]: (error) Shifting signed 32-bit value by 31 bits is undefined behaviour
4
5
Done!

von Wilhelm M. (wimalopaan)


Lesenswert?

Hier aber wieder nicht:
1
bool bad0() {
2
    for(uint8_t i{}; i < 33; i++) {
3
        uint32_t mask = 1 << i;
4
    }
5
    return true;
6
}

von Wilhelm M. (wimalopaan)


Lesenswert?

Ohne zusätzliche Tools schreibt man seine Tests einfach in 
constexpr-Kontexte, und alles UB wird erkannt.

von Oliver S. (oliverso)


Lesenswert?

Wilhelm M. schrieb:
> Hier aber wieder nicht:

Wenn du das (an der Stelle) fürchterliche i{} durch ein übliches i=0 
ersetzt, dann doch.

Oliver

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Oliver S. schrieb:
> Wilhelm M. schrieb:
>> Hier aber wieder nicht:
>
> Wenn du das (an der Stelle) fürchterliche i{} durch ein übliches i=0
> ersetzt, dann doch.

Ich finde es sehr praktisch und gar nicht fürchterlich.

Das ist uniform-initialization-syntax seit C++11 und cppcheck sollte das 
so langsam mal mitbekommen haben.

Dies zeigt aber das grundsätzliche Problem bei der Verwendung externer 
Werkzeuge.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Wilhelm M. schrieb:
> Dies zeigt aber das grundsätzliche Problem bei der Verwendung externer
> Werkzeuge.

Stimmt, deswegen schreibe ich inzwischen auch wieder
1
  if (x < 0) ...

statt
1
  if (x < int{}) ...

obwohl letzteres natürlich viel cooler ist ;-)

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


Lesenswert?

Wilhelm M. schrieb:
> Oliver S. schrieb:
>> Wilhelm M. schrieb:
>>> Hier aber wieder nicht:
>>
>> Wenn du das (an der Stelle) fürchterliche i{} durch ein übliches i=0
>> ersetzt, dann doch.
>
> Ich finde es sehr praktisch und gar nicht fürchterlich.

Ich finde, dass man den Wert, den man da in den Integer rein schreibt, 
durchaus sehen sollte, und das nicht nur, wenn er von 0 verschieden ist.

von Wilhelm M. (wimalopaan)


Lesenswert?

Yalu X. schrieb:
> Stimmt, deswegen schreibe ich inzwischen auch wieder
>   if (x < 0) ...

... würde ich jetzt bei std::is_same_v<decltype(x), std::string> == true 
nicht machen.

von Wilhelm M. (wimalopaan)


Lesenswert?

Wilhelm M. schrieb:
> Das ist uniform-initialization-syntax seit C++11 und cppcheck sollte das
> so langsam mal mitbekommen haben.

Sorry, schon seit C++03.

von Wilhelm M. (wimalopaan)


Lesenswert?

Rolf M. schrieb:
> Ich finde, dass man den Wert, den man da in den Integer rein schreibt,
> durchaus sehen sollte, und das nicht nur, wenn er von 0 verschieden ist.

Nennt sich value-initialization und ist in der generischen 
Programmierung sehr praktisch.

Aber selbst mit dem redundanten bzw. manchmal nicht möglichem 
Initialwert (hier bspw: 0) findet cppcheck das Problem nicht.

von Rolf M. (rmagnus)


Lesenswert?

Wilhelm M. schrieb:
> Rolf M. schrieb:
>> Ich finde, dass man den Wert, den man da in den Integer rein schreibt,
>> durchaus sehen sollte, und das nicht nur, wenn er von 0 verschieden ist.
>
> Nennt sich value-initialization und ist in der generischen
> Programmierung sehr praktisch.

Mir ist schon bewusst, dass das praktisch sein kann. An dieser Stelle 
ist es aber fehl am Platz. Hier haben wir auch keine generische 
Programmierung, sondern eine ganz simple Zählschleife, deren Zähler am 
Anfang mit einem Wert vorbelegt wird. Und den Wert an dieser Stelle nur 
deshalb zu verstecken, weil er zufälligerweise mit dem dem Default für 
die Initialisierung übereinstimmt, finde ich nicht sinnvoll. Man muss 
nicht jedes Feature überall nutzen, nur weil's halt geht.

> Aber selbst mit dem redundanten bzw. manchmal nicht möglichem
> Initialwert (hier bspw: 0) findet cppcheck das Problem nicht.

Das ist ein anderes Problem.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Rolf M. schrieb:
> Hier haben wir auch keine generische
> Programmierung, sondern eine ganz simple Zählschleife, deren Zähler am
> Anfang mit einem Wert vorbelegt wird.

Warum sollte man da einen Unterschied machen? Initialisierung immer mit 
{} und man muss sich nicht umstellen.

Rolf M. schrieb:
> weil er zufälligerweise mit dem dem Default für
> die Initialisierung übereinstimmt,

Zufällig ist hier nichts.

Rolf M. schrieb:
>> Aber selbst mit dem redundanten bzw. manchmal nicht möglichem
>> Initialwert (hier bspw: 0) findet cppcheck das Problem nicht.
>
> Das ist ein anderes Problem.

Sicher. Bedeutet aber, dass mir cppcheck nichts bringt.

Wie schon oben erläutert, halte ich das Vorgehen des TO bzw. das 
"übliche" Vorgehen für falsch.

Man kann es aber recht einfach lösen. Für eine Laufzeitlösung s.o. und 
eine Compilezeit-Lösung ist auch einfach:
1
    using m_t = mask_t<32>;
2
3
    m_t m{0b0000'00001};
4
    m <<= 33_c; // compiletime-error
5
    
6
    while(m.any()) {
7
        m <<= 1_c; // ok
8
    }

BTW: std::bitmask löst zwar das Vokabularproblem, aber hat sonst die 
übliche Semantik bzw. Möglichkeiten.

von Oliver S. (oliverso)


Lesenswert?

Wilhelm M. schrieb:
> Nennt sich value-initialization und ist in der generischen
> Programmierung sehr praktisch.

Da gehört das auch hin, in eine for-Schleife mit int aber nicht.

Allerdings ist das hier alles OT, für das Thema gibt ja einen eigenen 
Thread (in dem schon alles gesagt wurde).

Oliver

von Wilhelm M. (wimalopaan)


Lesenswert?

Oliver S. schrieb:
> Da gehört das auch hin, in eine for-Schleife mit int aber nicht.

Dein Argument ist: weil es immer so war, soll es so bleiben.

Mein Argument ist: Vereinheitlichung und damit Vereinfachung der 
Notation.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Wilhelm M. schrieb:
> Mein Argument ist: Vereinheitlichung und damit Vereinfachung der
> Notation.

Meine Kritik (und wohl auch die anderer Diskussionsteilnehmer hier)
bezieht sich nicht primär auf die Verwendung der geschweiften Klammern¹,
sondern auf das Weglassen der 0.

Einheitlich wäre es deswegen, wenn man wie bei

1
for(uint8_t i{1}; i < 32; i++)

auch beim Start mit 0 den Startwert explizit hinschreibt, also so:

1
for(uint8_t i{0}; i < 32; i++)


und nicht so:

1
for(uint8_t i{}; i < 32; i++)

Ich bin ja prinzipiell auch ein Freund knapper Schreibweisen, aber nur
dann, wenn man sie auch halbwegs durchgängig verwenden kann. Warum
sollte man bei den 4294967296 möglichden Initialisierungswerten für ein
int in einem einzigen Fall (nämlich für die 0) eine andere Schreibweise
verwenden?

Bei der generischen Programmierung sieht das natürlich anders aus. Dort
stehen die leeren geschweiften Klammern i.Allg. nicht für die numerische
Null, sondern für das neutrale Element eines Monoiden. Da dieses Element
etwas ganz Besonderes ist, darf auch seine Initialisierung etwas anders
aussehen.

―――――――――――――
¹) Dennoch empfinde ich sie als häßlich, weil sie keinerlei Bezug nur
   mathematischen Notation haben, wo eine Variablenbelegung immer und
   unabhängig vom Typ mit einem Gleichheitszeichen erfolgt.

von Rolf M. (rmagnus)


Lesenswert?

Yalu X. schrieb:
> Wilhelm M. schrieb:
>> Mein Argument ist: Vereinheitlichung und damit Vereinfachung der
>> Notation.
>
> Meine Kritik (und wohl auch die anderer Diskussionsteilnehmer hier)
> bezieht sich nicht primär auf die Verwendung der geschweiften Klammern¹,
> sondern auf das Weglassen der 0.

Ja, zumindest meine auch. Daher passt diese Frage eigentlich genau dazu:

Wilhelm M. schrieb:
> Warum sollte man da einen Unterschied machen?

Genau das ist eben auch mein Gedanke: Warum sollte man einen Unterschied 
machen, ob die Schleife jetzt bei 0 oder z.B. bei 42 beginnt?

von Wilhelm M. (wimalopaan)


Lesenswert?

Yalu X. schrieb:
> Einheitlich wäre es deswegen, wenn man wie bei
> auch beim Start mit 0 den Startwert explizit hinschreibt, also so:

Meine Aussage bezieht sich nicht (nur) auf die Schreibweise, sondern 
eben auf die Semantik:
1
template<typename T>
2
void bar() {
3
    T v; // default-initialization: non-class type -> indeterminate value
4
    T v1{}; // value-initialization: non-class type -> zero-initialization
5
    T v2{0}; // direct-initialization: non-class type -> no narrowing; class-type -> need ctor callable with one argument
6
    T v3 = 0; // copy-initialization: non-class-type -> narrowing; class-type -> need conversion-ctor
7
    T v4(); // function declaration
8
    while(!end(v)) {
9
        ++v;
10
    }
11
}

Im Beispiel oben sieht man eben den Unterschied: ggf. ist es für einen 
Datentyp T gar nicht möglich. Die geringste implizite 
template-Typanforderung ist die default-Konstruktion über die 
value-initialization. Und bei primitiven DT resultiert 
zero-initialization. Das ist ganz eindeutig und klar.

Yalu X. schrieb:
> Dennoch empfinde ich sie als häßlich, weil sie keinerlei Bezug nur
>    mathematischen Notation haben, wo eine Variablenbelegung immer und
>    unabhängig vom Typ mit einem Gleichheitszeichen erfolgt.

Mathematische Variablen und benannte Objekte aka Variablen im Kontext 
einer imperativen Programmiersprache sind zwei unterschiedliche Dinge.

von Oliver S. (oliverso)


Lesenswert?

Wilhelm M. schrieb:
> Im Beispiel oben sieht man eben den Unterschied: ggf. ist es für einen
> Datentyp T gar nicht möglich.

Das ist aber genau der Punkt.

Wilhelm M. schrieb:
> for(uint8_t i{}; i < 33; i++) {

Da gibt es kein T und keine semantisch erforderliche 
default-Initialisierung.

Da steht ein POD uint8_t, und dessen Semantik ist "Startwert der 
Schleifenvariablen". Da nicht die 0 hinzuschreiben, geht am Ziel vorbei.

Oliver

: Bearbeitet durch User
von Yalu X. (yalu) (Moderator)


Lesenswert?

Wilhelm M. schrieb:
> T v1{}; // value-initialization: non-class type -> zero-initialization
> T v2{0}; // direct-initialization: non-class type -> no narrowing;
> class-type -> need ctor callable with

Und was spricht jetzt genau dagegen, für die Laufvariable mit dem festen
Typ uint8_t die direct-initialization statt der value-initialization zu
verwenden?

Das kostet zwar einen Tastendruck mehr, dafür hätten aber alle
Zählschleifen unabhängig von ihrem Startwert ein einheitliches Aussehen,
und Vereinheitlichung scheint ja auch dir wichtig zu sein:

Wilhelm M. schrieb:
> Mein Argument ist: Vereinheitlichung und damit Vereinfachung der
> Notation.


Wilhelm M. schrieb:
> Im Beispiel oben sieht man eben den Unterschied: ggf. ist es für einen
> Datentyp T gar nicht möglich. Die geringste implizite
> template-Typanforderung ist die default-Konstruktion über die
> value-initialization.

Dass die value-initialization in der generischen Programmierung sinnvoll
sein kann, habe ich ja oben bereits geschrieben. Im konkreten Beispiel
mit der Schleife ist aber nichts generisch, sondern wir haben es mit
einem klar festgelegten Typ (uint8_t) zu tun.


Wilhelm M. schrieb:
> Yalu X. schrieb:
>> Dennoch empfinde ich sie als häßlich, weil sie keinerlei Bezug nur
>>    mathematischen Notation haben, wo eine Variablenbelegung immer und
>>    unabhängig vom Typ mit einem Gleichheitszeichen erfolgt.
>
> Mathematische Variablen und benannte Objekte aka Variablen im Kontext
> einer imperativen Programmiersprache sind zwei unterschiedliche Dinge.

Das ist klar. Trotzdem finde ich es praktisch, wenn sich die Syntax
einer Programmiersprache an bereits bekannte Schreibweisen anlehnt, die
bspw. von

- der natürlichen Sprache (wie bspw. bei "if", "while" und "return"),

- der Mathematik (wie bspw. "+", "-", "sin") oder

- anderen bereits verbreiteten Programmiersprachen

übernommen werden können.

In den allermeisten Programmiersprachen (insbesondere auch den
imperativen) wird nun mal mit einem Gleichheitszeichen initialisiert.
Nur C++ entfernt sich immer mehr von dieser Konvention.

von Rolf Magnus (Gast)


Lesenswert?

Yalu X. schrieb:
> Wilhelm M. schrieb:
>> Im Beispiel oben sieht man eben den Unterschied: ggf. ist es für einen
>> Datentyp T gar nicht möglich. Die geringste implizite
>> template-Typanforderung ist die default-Konstruktion über die
>> value-initialization.
>
> Dass die value-initialization in der generischen Programmierung sinnvoll
> sein kann, habe ich ja oben bereits geschrieben. Im konkreten Beispiel
> mit der Schleife ist aber nichts generisch, sondern wir haben es mit
> einem klar festgelegten Typ (uint8_t) zu tun.

Wobei das hier nicht mal eine Rolle spielt. Selbst wenn es generisch 
wäre: Wenn ich eine Zählschleife habe und die explizit bei 0 starten 
lassen will, schreibe ich 0 als Startwert hin, egal von welchem Typ mein 
Zähler ist. Und ich schreibe den Wert hin, egal ob er 0 oder 1 oder 
11149 ist.
Die Semantik ist eben "ich möchte bei 0 anfangen zu zählen" und nicht 
"ich möchte bei dem default-Wert des Datentyps anfangen".

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.