Forum: Mikrocontroller und Digitale Elektronik Operatoren verstehen


von Mark (Gast)


Lesenswert?

Hi

Angenommen ich möchte if(isActivated() && i++) ausführen.
i++ wird nur erhöht, wenn isActivated() true ist.

sorgt if(isActivated() & i++) dafür, dass beide Seiten ausgewertet 
werden?
Oder wird einfach bitweise ausgewertet?
zB
uint8_t i=2;
isActivated ist true, also 1;
Bei 8 bit also:
isActivated -> true > 00000001
i       -> 2 ->       00000010
ergibt 0, und ich komme nicht in die Schleife..?

von Dergute W. (derguteweka)


Lesenswert?

Moin,

Ich (persoenlich) wuerd' mir ueber solche Konstrukte garnicht lange 
einen Kopf machen, sondern so abaendern, dass man mit einem Blick 
erkennen kann, was das macht. Also in mehrere ifs aufspalten.
Wenn man sich solche Sachen dann alle halbe Jahre mal gucken muss, und 
jedesmal ins Grueblen kommt, was da jetzt wieder gemeint war, ist das 
nicht gut; egal wie "elegant" einem das momentan vorkommen mag.


Gruss
WK

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Mark schrieb:
> sorgt if(isActivated() & i++) dafür, dass beide Seiten ausgewertet
> werden?

Es sorgt auf jeden Fall für graue Haare bei dem Programmierer, der sowas 
nach drei Jahren mal anfassen muss. Praktisch jeder würde wohl erstmal 
das "&" gedanklich als "&&" ansehen …

von Brummbär (Gast)


Lesenswert?

Mark schrieb:
> und ich komme nicht in die Schleife..?

Welche Schleife?

von Mark (Gast)


Lesenswert?

Brummbär schrieb:
> Mark schrieb:
>> und ich komme nicht in die Schleife..?
>
> Welche Schleife?

Sorry, zu schnell geschrieben.

Jörg W. schrieb:
> Mark schrieb:
>> sorgt if(isActivated() & i++) dafür, dass beide Seiten ausgewertet
>> werden?
>
> Es sorgt auf jeden Fall für graue Haare bei dem Programmierer, der sowas
> nach drei Jahren mal anfassen muss. Praktisch jeder würde wohl erstmal
> das "&" gedanklich als "&&" ansehen …

Es geht mir weniger darum, wie ich es später mache oder ob ich das so 
anwende. Ich möchte es einfach verstehen.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Mark schrieb:
> Angenommen ich möchte if(isActivated() && i++) ausführen.
> i++ wird nur erhöht, wenn isActivated() true ist.

Richtig. Nennt sich "Short-Circuit Evaluation".

Mark schrieb:
> sorgt if(isActivated() & i++) dafür, dass beide Seiten ausgewertet
> werden?
Ja.

Dann sollte man im Kommentar aber dazu schreiben, dass das Absicht ist - 
es sieht stark nach Tippfehler aus!

Mark schrieb:
> ergibt 0, und ich komme nicht in die Schleife..?
Fast. Wenn "i" 1 ist, ist "i++" auch 1. Du möchtest vermutlich "++i". Du 
könntest
1
if(!!isActivated() & !!(++i))
oder (C++)
1
if(bool { isActivated() } & bool { i++ })
schreiben. Aber doch beides eher hässlich. Warum nicht einfach
1
if (++i && isActivated())
Dann wird "++i" wie gewünscht immer ausgeführt. Ob "isActivated" 
ausgeführt wird oder nicht sollte ja keinen Unterschied machen - oder 
hat eine Funktion, die ihrem Namen nach keine Seiteneffekte haben 
sollte, doch welche?!

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Mark schrieb:
> Ich möchte es einfach verstehen.

Ohne jetzt genau im Standard nachzusehen, würde ich mal sagen, dass das 
undefined behaviour ist. Die „Kurzschluss-Auswertung“ ist nur bei den 
Operatoren && / || im Standard vorgeschrieben. Damit ist es nicht 
sicher, ob das "++" von "i++" tatsächlich ausgeführt wird oder nicht.

von Brummbär (Gast)


Lesenswert?

Wenn i immer erhöht werden soll, dann mach es doch so:
1
if (isActivated() && i) { ... }
2
i++;

Soll i nur dann erhört werden wenn isActivated() und i == 0:
1
if (isActivated() && !i) { i++; ... }

Beitrag #5703971 wurde vom Autor gelöscht.
von Bauform B. (bauformb)


Lesenswert?

Mark schrieb:
> isActivated ist true, also 1;
> Bei 8 bit also:
> isActivated -> true > 00000001

Ist das irgendwo festgelegt? Gibt es in C überhaupt true und false? Hast 
du die volle Kontrolle über den Rückgabewert von isActivated()? Ich 
glaube nicht.

Wer so kompakt programmiert, ändert irgendwann auch isActivated() innen 
drin und das liefert dann != 0 für true, was ja fast immer dasselbe 
ist wie 1...

von Mark (Gast)


Lesenswert?

Brummbär schrieb:
> Wenn i immer erhöht werden soll, dann mach es doch so:if
> (isActivated() && i) { ... }
> i++;
>
> Soll i nur dann erhört werden wenn isActivated() und i == 0:if
> (isActivated() && !i) { i++; ... }

Ich frage aufgrund des letzten Posts:
https://www.informatik-forum.at/forum/index.php?thread/3856-oder-und-oder/

Wenn ich zB Flags überprüfen möchte und zwar 2 gleichzeitig
nehme ich also &&
if (__HAL_ADC_GET_FLAG(ADC, flag1) && __HAL_ADC_GET_FLAG(ADC, flag2)
...

weil ja sowieso beide 1 sein müssen und wenn linke Seite bereits falsch 
ist, interessiert der rechte Teile ja sowieso nicht.
Stimmts?

von Mark (Gast)


Lesenswert?

Niklas G. schrieb:
> Mark schrieb:
Du
> könntestif(!!isActivated() & !!(++i))oder (C++)if(bool { isActivated() }


Ich kenne ! und zB !=
Was ist !! --> not not = Urspungswert?

von Mampf F. (mampf) Benutzerseite


Lesenswert?

Mark schrieb:
> Niklas G. schrieb:
>> Mark schrieb:
> Du
>> könntestif(!!isActivated() & !!(++i))oder (C++)if(bool { isActivated() }
>
> Ich kenne ! und zB !=
> Was ist !! --> not not = Urspungswert?

!! macht aus allem ungleich 0 eine 1

Das ist zB ganz praktisch für bitvergleiche ...

!!(value & 0x40) ergibt 1 wenn das Bit gesetzt ist und 0 wenn nicht.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Niklas G. schrieb:
> Du könntestif(!!isActivated() & !!(++i))

Nein, das ändert nichts.

Nochmal, Kurzschluss-Evaluierung ist nur für && und || festgelegt.

Bei & (oder |) ist es dem Compiler frei gestellt, in welcher Reihenfolge 
er die einzelnen Teilausdrücke bewertet, da ändert auch eine doppelte 
Negation nichts dran.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Jörg W. schrieb:
> Nein, das ändert nichts.

Ich hatte es so verstanden, dass er beide Ausdrücke auswerten will und 
er eben keine Short-Circuit-Evaluation möchte. IIRC ist bei "&" 
garantiert, dass es immer alle Ausdrücke auswertet. Das Problem ist nur 
der Wert des Ausdrucks, weshalb mit !! ein bool draus gemacht wird.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Niklas G. schrieb:
> IIRC ist bei "&" garantiert, dass es immer alle Ausdrücke auswertet.

Keineswegs.  Auch da darf der Compiler Dinge weglassen, die überflüssig 
sind.

Ich bin mir im initial genannten Fall jedoch nicht sicher, ob er das 
Inkrementieren von i tatsächlich immer durchziehen muss oder nicht, da 
müsste ich den Standard nachlesen. Wie ich aber schon eingangs schrieb, 
würde ich solch einen Konstrukt selbst dann vermeiden, wenn ich wüsste, 
was dem Compiler hier vorgeschrieben ist und was wahlfrei ist. :)

von W.S. (Gast)


Lesenswert?

Mark schrieb:
> Angenommen ich möchte if(isActivated() && i++) ausführen.

Warum geht das eigentlich nicht in den Kreisen von C-Programmierern, 
sich zu verkneifen, jede nur irgendwie erdenkliche Möglichkeit 
auszureizen, die die Sprache nicht rundweg verbietet - anstatt dasselbe 
auf gut leserliche, klare und einfache Weise auszudrücken?

W.S.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Warum geht es eigentlich nicht in den Kreisen von C-Hassern, sich aus 
Threads bezüglich bestimmter Eigenschaften dieser Programmiersprache 
einfach mal herauszuhalten?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Jörg W. schrieb:
> Keineswegs.  Auch da darf der Compiler Dinge weglassen, die überflüssig
> sind.

Hier
https://stackoverflow.com/a/1758623
heißt es:

The & operator performs logical "and" operation for bool operands and is 
not short circuited.

It's not a sequence point. You cannot rely on the order of evaluation of 
the operands. However, it's *guaranteed that both operands are 
evaluated*.
1
#include <stdio.h>
2
int foo () {
3
  puts ("foo");
4
  return 7;
5
}
6
7
int main () {
8
  if (0 & foo()) {
9
    puts ("ok");
10
  }
11
}
Dieses Programm gibt "foo" aus, ohne Warnung von GCC oder Clang. UB oder 
nicht?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Niklas G. schrieb:
> UB oder nicht?

Kann man natürlich nur aus dem Verhalten einzelner Compiler erst einmal 
nicht schließen – UB kann ja durchaus auch bedeuten, dass trotzdem genau 
das passiert, was man sich unter diesem Ausdruck vorstellen würde …

Nach Überfliegen des Standards tendiere ich dazu, die Erklärung auf 
Stackoverflow für korrekt zu befinden.

Ich würde solch eine Konstruktion trotzdem nicht selbs benutzen, auch 
nicht mit dem Wissen darum, dass sie legal und vorhersagbar ist. ;-)

von Luther B. (luther-blissett)


Lesenswert?

Niklas G. schrieb:

> It's not a sequence point. You cannot rely on the order of evaluation of
> the operands. However, it's *guaranteed that both operands are
> evaluated*.

Das sind die entscheidenden Hinweise. foo() & i++ kann bedeuten
1
t0=foo()
2
t1=i
3
i=i+1
4
=> t0 & t1

oder
1
t1=i
2
t0=foo()
3
i=i+1
4
=> t0 & t1

oder
1
t1=i
2
i=i+1
3
t0=foo()
4
=> t0 & t1

Sollte foo() i lesen oder schreiben, dann kan man jeweils ein anderes 
Ergebnis und/oder einen anderen Wert für i erhalten.

von Mark (Gast)


Lesenswert?

wenn ihr sagt Standard lesen, wo lest ihr das nach?
Wo finde ich den richtigen C-Standard?

von Cyblord -. (cyblord)


Lesenswert?

Mark schrieb:
> wenn ihr sagt Standard lesen, wo lest ihr das nach?
> Wo finde ich den richtigen C-Standard?

Es gibt nicht "den richtigen C-Standard". C99 wird oft verwendet. Ist 
aber kein muss. Es gibt noch C89 z.B.

von Mark (Gast)


Lesenswert?

Cyblord -. schrieb:
> Mark schrieb:
>> wenn ihr sagt Standard lesen, wo lest ihr das nach?
>> Wo finde ich den richtigen C-Standard?
>
> Es gibt nicht "den richtigen C-Standard". C99 wird oft verwendet. Ist
> aber kein muss. Es gibt noch C89 z.B.

das?
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Mark schrieb:
> Wo finde ich den richtigen C-Standard?

Den richtigen Standard gibt's nur gegen Geld.

Bei der entsprechenden Standardisierungsgruppe der ISO:

http://www.open-std.org/JTC1/SC22/WG14/www/projects#9899

gibt es jedoch Dokumente

http://www.open-std.org/JTC1/SC22/WG14/www/standards

die im Falle des N1256 de facto dem Standard C99 mit eingearbeiteten 
Korrigenda entspricht bzw. im Falle von N1570 dem Stand, welcher dann 
zur Verabschiedung durch die ISO als C11-Standard gebracht worden ist.

Damit hat man nunmehr schon seit vielen Jahren auch als Otto 
Normalenwickler die Chance, de facto den gültigen Standard als 
„Gesetzestext“ einzusehen. Bei C99 gibt's sogar noch ein 
„Rationale“-Dokument, welches bestimmte getroffene Entscheidungen 
motiviert.

von Mark (Gast)


Lesenswert?

Jörg W. schrieb:
> Mark schrieb:
>> Wo finde ich den richtigen C-Standard?
>
> Den richtigen Standard gibt's nur gegen Geld.
>
> Bei der entsprechenden Standardisierungsgruppe der ISO:
>
> http://www.open-std.org/JTC1/SC22/WG14/www/projects#9899
>
> gibt es jedoch Dokumente
>
> http://www.open-std.org/JTC1/SC22/WG14/www/standards
>

> motiviert.

super, danke.

von Mark (Gast)


Lesenswert?

Wenn ich zb 4 Flags prüfen muss:

if (__HAL_ADC_GET_FLAG(ADC, flag1) && __HAL_ADC_GET_FLAG(ADC, flag2)&&& 
__HAL_ADC_GET_FLAG(ADC, flag4)&&& __HAL_ADC_GET_FLAG(ADC, flag4))

wird das Ganze schon nicht mehr wirklich übersichtlich

würde das so funktionieren?

#define ALLFLAGSACTIVE() (__HAL_ADC_GET_FLAG(ADC, flag1) && 
__HAL_ADC_GET_FLAG(ADC, flag2)&&& __HAL_ADC_GET_FLAG(ADC, flag4)&&& 
__HAL_ADC_GET_FLAG(ADC, flag4))

und dann mit
if (ALLFLAGSACTIVE()){...
}
weiterfahren?

von Mark (Gast)


Lesenswert?

Mark schrieb:
> Wenn ich zb 4 Flags prüfen muss:
>
> if (__HAL_ADC_GET_FLAG(ADC, flag1) && __HAL_ADC_GET_FLAG(ADC, flag2)&&&
> __HAL_ADC_GET_FLAG(ADC, flag4)&&& __HAL_ADC_GET_FLAG(ADC, flag4))
>
> wird das Ganze schon nicht mehr wirklich übersichtlich
>
> würde das so funktionieren?
>
> #define ALLFLAGSACTIVE() (__HAL_ADC_GET_FLAG(ADC, flag1) &&
> __HAL_ADC_GET_FLAG(ADC, flag2)&&& __HAL_ADC_GET_FLAG(ADC, flag4)&&&
> __HAL_ADC_GET_FLAG(ADC, flag4))
>
> und dann mit
> if (ALLFLAGSACTIVE()){...
> }
> weiterfahren?

nicht &&&
stattdessen &&

von Dergute W. (derguteweka)


Lesenswert?

Moin,

Mark schrieb:
> Wenn ich zb 4 Flags prüfen muss:
>
> if (__HAL_ADC_GET_FLAG(ADC, flag1) && __HAL_ADC_GET_FLAG(ADC, flag2)&&&
> __HAL_ADC_GET_FLAG(ADC, flag4)&&& __HAL_ADC_GET_FLAG(ADC, flag4))
>
> wird das Ganze schon nicht mehr wirklich übersichtlich
>
> würde das so funktionieren?
>
> #define ALLFLAGSACTIVE() (__HAL_ADC_GET_FLAG(ADC, flag1) &&
> __HAL_ADC_GET_FLAG(ADC, flag2)&&& __HAL_ADC_GET_FLAG(ADC, flag4)&&&
> __HAL_ADC_GET_FLAG(ADC, flag4))
>
> und dann mit
> if (ALLFLAGSACTIVE()){...
> }
> weiterfahren?

Moin,

Dann schreibste's so hin (dann fallen dir auch alle fehlenden Klammern, 
etc. sofort ins Auge) :
1
if ((__HAL_ADC_GET_FLAG(ADC, flag1) &&
2
    (__HAL_ADC_GET_FLAG(ADC, flag2) &&
3
    (__HAL_ADC_GET_FLAG(ADC, flag3) &&
4
    (__HAL_ADC_GET_FLAG(ADC, flag4)) ...

und schon kann man's auch in ein paar Monaten noch recht schnell 
sinnerfassend lesen.
Wenn du jetzt noch ein ALLFLAGSACTIVE() Macro reinmanschst, musst du 
dann immer erst nochmal nachgucken, was fuer aktive Flags "ALL" damals 
genau beinhaltete.

Gruss
WK

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Im Prinzip würde das wohl gehen. (Willst du wirklich "&&"?)

Aber das zeigt auch die Grenzen dieses HAL auf: vermutlich gehen alle 
diese __HAL_ADC-Makros in das gleiche Statusregister, und es wäre viel 
einfacher, dieses Register einmal abzufragen und gleichzeitig auf alle 4 
Bits zu testen, statt da viermal in die Hardware zu gehen …

von Mark (Gast)


Lesenswert?

Dergute W. schrieb:
> Moin,
>
> Mark schrieb:
>> Wenn ich zb 4 Flags prüfen muss:
>>
>> if (__HAL_ADC_GET_FLAG(ADC, flag1) && __HAL_ADC_GET_FLAG(ADC, flag2)&&&
>> __HAL_ADC_GET_FLAG(ADC, flag4)&&& __HAL_ADC_GET_FLAG(ADC, flag4))
>>
>> wird das Ganze schon nicht mehr wirklich übersichtlich
>>
>> würde das so funktionieren?
>>
>> #define ALLFLAGSACTIVE() (__HAL_ADC_GET_FLAG(ADC, flag1) &&
>> __HAL_ADC_GET_FLAG(ADC, flag2)&&& __HAL_ADC_GET_FLAG(ADC, flag4)&&&
>> __HAL_ADC_GET_FLAG(ADC, flag4))
>>
>> und dann mit
>> if (ALLFLAGSACTIVE()){...
>> }
>> weiterfahren?
>
> Moin,
>
> Dann schreibste's so hin (dann fallen dir auch alle fehlenden Klammern,
> etc. sofort ins Auge) :
> if ((__HAL_ADC_GET_FLAG(ADC, flag1) &&
>     (__HAL_ADC_GET_FLAG(ADC, flag2) &&
>     (__HAL_ADC_GET_FLAG(ADC, flag3) &&
>     (__HAL_ADC_GET_FLAG(ADC, flag4)) ...
> und schon kann man's auch in ein paar Monaten noch recht schnell
> sinnerfassend lesen.
> Wenn du jetzt noch ein ALLFLAGSACTIVE() Macro reinmanschst, musst du
> dann immer erst nochmal nachgucken, was fuer aktive Flags "ALL" damals
> genau beinhaltete.
>
> Gruss
> WK

super Tipp, danke

Jörg W. schrieb:
> Im Prinzip würde das wohl gehen. (Willst du wirklich "&&"?)
>
> Aber das zeigt auch die Grenzen dieses HAL auf: vermutlich gehen alle
> diese __HAL_ADC-Makros in das gleiche Statusregister, und es wäre viel
> einfacher, dieses Register einmal abzufragen und gleichzeitig auf alle 4
> Bits zu testen, statt da viermal in die Hardware zu gehen …

auch danke vielmals
Meinst du so:
ADC.instance->Registername & Flagmaske == ADC.instance->Registername | 
Flagmaske

von Mark (Gast)


Lesenswert?

Jörg W. schrieb:
> (Willst du wirklich "&&"?)
>

Wieso nicht, es müssen doch alle flags gesetzt sein.

von Arc N. (arc)


Lesenswert?

Jörg W. schrieb:
> Im Prinzip würde das wohl gehen. (Willst du wirklich "&&"?)
>
> Aber das zeigt auch die Grenzen dieses HAL auf: vermutlich gehen alle
> diese __HAL_ADC-Makros in das gleiche Statusregister, und es wäre viel
> einfacher, dieses Register einmal abzufragen und gleichzeitig auf alle 4
> Bits zu testen, statt da viermal in die Hardware zu gehen …

Richtig. Das Makro ist definiert als
1
#define __HAL_ADC_GET_FLAG(__HANDLE__, __FLAG__) ((((__HANDLE__)->Instance->ISR) & (__FLAG__)) == (__FLAG__))

In stm32l4xx_hal_adc.h gibt es dazu ADC_FLAG_ALL in der einfach alle 
Flags verodert sind.

von Mark (Gast)


Lesenswert?

Arc N. schrieb:
> Jörg W. schrieb:
>> Im Prinzip würde das wohl gehen. (Willst du wirklich "&&"?)
>>
>> Aber das zeigt auch die Grenzen dieses HAL auf: vermutlich gehen alle
>> diese __HAL_ADC-Makros in das gleiche Statusregister, und es wäre viel
>> einfacher, dieses Register einmal abzufragen und gleichzeitig auf alle 4
>> Bits zu testen, statt da viermal in die Hardware zu gehen …
>
> Richtig. Das Makro ist definiert als#define
> __HAL_ADC_GET_FLAG(_HANDLE_, _FLAG_) ((((_HANDLE_)->Instance->ISR)
> & (_FLAG_)) == (_FLAG_))
>
> In stm32l4xx_hal_adc.h gibt es dazu ADC_FLAG_ALL in der einfach alle
> Flags verodert sind.

Mit all meine ich zB nur flag1,2 und 3 anstatt zB alle wie die HAL 
wahrscheinlich mit flag1,2,3,4,5...
Also muss ich selbst eine Maske definieren.

Ich meine so müsste es gehen
ADC.instance->Registername & Flagmaske == ADC.instance->Registername |
Flagmaske

Mark schrieb:
> Jörg W. schrieb:
>> (Willst du wirklich "&&"?)
>>
>
> Wieso nicht, es müssen doch alle flags gesetzt sein.

Warum sollte ich mir da nicht sicher sein bzgl. &&?
Kann mir das jemand verraten?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Mark schrieb:
> Ich meine so müsste es gehen
> ADC.instance->Registername & Flagmaske == ADC.instance->Registername |
> Flagmaske

Nein. Da greifst du das Register doch zweimal zu.

Du brauchst
1
  if ((ADC.instance->Registername & Flagmaske) == Flagmaske)
2
    // ...

>> Jörg W. schrieb:
>>> (Willst du wirklich "&&"?)
>> Wieso nicht, es müssen doch alle flags gesetzt sein.
>
> Warum sollte ich mir da nicht sicher sein bzgl. &&?
> Kann mir das jemand verraten?

Ich hätte erwartet, dass du dich dafür interessierst, ob irgendeins 
der Flags gesetzt ist, nicht alle miteinander. Aber ich kenne die 
Hardware überhaupt nicht.

von Mark (Gast)


Lesenswert?

Jörg W. schrieb:
> Mark schrieb:
>> Ich meine so müsste es gehen
>> ADC.instance->Registername & Flagmaske == ADC.instance->Registername |
>> Flagmaske
>
> Nein. Da greifst du das Register doch zweimal zu.
>
> Du brauchst
>   if ((ADC.instance->Registername & Flagmaske) == Flagmaske)
>     // ...
>

Die REgister haben 32-bit.
Wenn ich beispielsweise ein Register habe, bei dem die oberen 16-bit die 
Register darstellen, funktioniert dein Ansatz ja gar nicht.
Woher soll ich wissen wie die unteren 16-bits definiert sind und das in 
die Flagmaske packen?
Was ist, wenn die unteren 16-bit nicht fix, sondern also variabel sind?

von Mark (Gast)


Lesenswert?

Mark schrieb:
> Jörg W. schrieb:
>> Mark schrieb:
>>> Ich meine so müsste es gehen

>> Du brauchst
>>   if ((ADC.instance->Registername & Flagmaske) == Flagmaske)
>>     // ...
>>
>
> Die REgister haben 32-bit.
> Wenn ich beispielsweise ein Register habe, bei dem die oberen 16-bit die
> Register darstellen,

ich meine statt REgister --> Flags darstellen

von Mark (Gast)


Lesenswert?

Mark schrieb:
> Mark schrieb:
>> Jörg W. schrieb:
>>> Mark schrieb:
>>>> Ich meine so müsste es gehen
>
>>> Du brauchst
>>>   if ((ADC.instance->Registername & Flagmaske) == Flagmaske)
>>>     // ...
>>>
>>
>> Die REgister haben 32-bit.
>> Wenn ich beispielsweise ein Register habe, bei dem die oberen 16-bit die
>> Register darstellen,
>
> ich meine statt REgister --> Flags darstellen

Ich könnte aber auf das Register einmal zugreifen, diesen einer 
poiterVariable zuweisen und den Wert der pVariable vergleichen.
Ich weiss nicht ob das schneller geht oder effektiver ist. Dann greife 
ich nur einmal auf das Register zu.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Mark schrieb:
> Die REgister haben 32-bit.

Ja, und?  Die Flagmaske auch, die muss natürlich ohnehin passend zum 
Register sein.  Wenn die Registerwerte in den oberen 16 Bit stehen, 
müssen die Maskenbits auch in den oberen 16 Bit stehen (und die unteren 
sind 0).

Die linke Operation "&" extrahiert ja aus dem eigentlichen Registerwert 
(und was auch sonst noch alles dabei ist) nur die dich 
interessierenden Bits ("Flagmaske"), der Vergleich wiederum ist dann 
wahr, wenn alle diese Bits auch wirklich gesetzt sind.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Mark schrieb:
> Ich könnte aber auf das Register einmal zugreifen, diesen einer
> poiterVariable zuweisen und den Wert der pVariable vergleichen.

Ich glaube, du stocherst gerade wild durch die Kante ohne eine Idee zu 
haben, was deine einzelnen Aktionen überhaupt tatsächlich 
bewerkstelligen.

von Egon D. (Gast)


Lesenswert?

W.S. schrieb:

> Mark schrieb:
>> Angenommen ich möchte if(isActivated() && i++)
>> ausführen.
>
> Warum geht das eigentlich nicht in den Kreisen von
> C-Programmierern, sich zu verkneifen, jede nur
> irgendwie erdenkliche Möglichkeit auszureizen, die
> die Sprache nicht rundweg verbietet - anstatt
> dasselbe auf gut leserliche, klare und einfache
> Weise auszudrücken?

Weil der durchschnittliche Anfänger natürlich von der
berechtigten Annahme ausgeht, dass die Sprache alle
Möglichkeiten, die sie bietet, AUS GUTEM GRUND bietet.

Was im Jahre 1972 ein guter Grund war, muss im Jahre
2022, also zum fünfzigsten Geburtstag der Sprache C,
natürlich nicht unbedingt immer noch ein guter Grund
sein.
Das ist dem Anfänger aber nicht klar, und ich weiss
nicht, ob die üblichen Bücher deutlich genug auf diese
Tatsache hinweisen.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Andererseits ist natürlich nicht alles, was der geneigte 
Pascal-Programmierer an C nicht versteht, nur eben deshalb ein 
Misfeature, weil es ein Pascal-Programmierer nicht (auf Anhieb) 
versteht.  Das fängt eben damit an, dass Pascal keine (im Sprachstandard 
festgezurrte) shortcut evaluation Boolscher Operatoren hatte, sodass man 
immer zwei IF-Anweisungen schachteln musste, wenn man zuerst einen 
Pointer auf <> nil testen und danach über ihn zugreifen wollte 
(zumindest, wenn man ein standardkonformes Programm haben wollte). Ein 
C-Programmierer wird sich bei sowas wohl stets auf die shortcut 
evaluation verlassen wollen, denn sie erlaubt es, Dinge einfacher zu 
schreiben (weniger Schachtelung).

Selbst der gern geschmähte ternäre Operator kann in seltenen Fällen 
seine Berechtigung haben, weil man mit ihm manchmal die Lesbarkeit eines 
Programms verbessern kann.

: Bearbeitet durch Moderator
von Egon D. (Gast)


Lesenswert?

Jörg W. schrieb:

> Andererseits ist natürlich nicht alles, was der
> geneigte Pascal-Programmierer an C nicht versteht,
> nur eben deshalb ein Misfeature, weil es ein
> Pascal-Programmierer nicht (auf Anhieb) versteht.

Richtig.

Du kannst aber daraus, dass ein Pascal-Programmierer
ein Feature von C ablehnt, nicht zuverlässig folgern,
dass er es nicht versteht :)


> Das fängt eben damit an, dass Pascal keine (im
> Sprachstandard festgezurrte) shortcut evaluation
> Boolscher Operatoren hatte, sodass man immer zwei
> IF-Anweisungen schachteln musste, wenn man zuerst
> einen Pointer auf <> nil testen und danach über
> ihn zugreifen wollte (zumindest, wenn man ein
> standardkonformes Programm haben wollte).

Richtig; Du nimmst mir fast das Wort aus dem Mund.

Das Problem liegt auch überhaupt nicht darin, dass
es diese abgekürzte Auswertung GIBT (und auch nicht
darin, dass sie im Standard verankert ist), sondern
darin, dass sie auf maximal idiotische Weise die
Steuerstruktur des Programmes (neudeutsch und falsch:
"Kontrollstruktur") mit der Auswertung boolescher
Ausdrücke zusammenmatscht.

Nach meinem Anfängerverständnis ist "&&" eine Abkürzung
für
1
if (...) { 
2
  if (...) { 
3
  } 
4
}

und das hat mit der Auswertung boolescher Ausdrücke
ungefähr so viel zu tun wie der Mond mit dem
Vierwaldstätter See.
Boolesche Operatoren (also die "echten" booleschen
Operatoren, die auch das machen, was die Algebra
vorschreibt) sind kommutativ, d.h. a XOR b ist immer
und unter allen Umständen dasselbe wie b XOR a.
Für die STEUERSTRUKTUR, d.h. den Ablaufpfad des
Programmes stimmt das auch für "&&" und "||" -- NICHT
ABER für die Seiteneffekte. Das ist eine ziemlich
bescheuerte Falle.

Dagegen löst "&" ja TATSÄCHLICH eine echte boolesche
Verknüpfung der Operanden aus, die dann logischerweise
kommutativ ist.

Bei natürlichen Sprachen würde man das einen "false
friend" nennen.


> Selbst der gern geschmähte ternäre Operator kann in
> seltenen Fällen seine Berechtigung haben, weil man
> mit ihm manchmal die Lesbarkeit eines Programms
> verbessern kann.

Sicher -- das wurde ja vor Monaten schon beim "goto"
durchdiskutiert, und da wurden auch gute und gültige
Argumente genannt: Es gibt halt Sprachelemente, die
man zwar selten braucht, die aber dennoch in diesen
seltenen Fällen gute Dienste leisten.

Es hat bei C keinen Sinn mehr, über die Sprache an
sich zu diskutieren -- aber sehr wohl kann man die
heutige Verwendung dieser Sprache im Detail diskutieren.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Egon D. schrieb:
> Boolesche Operatoren (also die "echten" booleschen Operatoren, die auch
> das machen, was die Algebra vorschreibt) sind kommutativ

Die echte Boolsche Algebra kennt aber auch keine Operanden mit 
Seiteneffekten, und da reden wir jetzt nicht nur über sowas wie ++ oder 
--, sondern eben auch darüber, dass man dann dann auch keine 
Funktionsaufrufe mehr als Operanden haben dürfte.

Mich haben jedenfalls seinerzeit die notwendig doppelt geschachtelten IF 
für Zeigervariablen immer gestört, weil man damit zwangsweise zwei 
Einrückungs-Niveaus gebraucht hat, von denen eins bis auf die zweite 
IF-Anweisung völlig leer war.

von Egon D. (Gast)


Lesenswert?

Jörg W. schrieb:

> Egon D. schrieb:
>> Boolesche Operatoren (also die "echten" booleschen
>> Operatoren, die auch das machen, was die Algebra
>> vorschreibt) sind kommutativ
>
> Die echte Boolsche Algebra kennt aber auch keine
> Operanden mit Seiteneffekten,

Ja.


> und da reden wir jetzt nicht nur über sowas wie ++ oder
> --, sondern eben auch darüber, dass man dann dann auch
> keine Funktionsaufrufe mehr als Operanden haben dürfte.

Hmm. Nee. Einspruch.
Funktionsaufrufe müssen nicht notwendig Seiteneffekte
haben. Wenn in dem booleschen Ausdruck nur gutartige
Funktionen aufgerufen werden, die nur lesen, rechnen und
das Ergebnis zurückliefern, müsste alles in Butter sein.

Vielleicht können trotzdem Probleme mit der nicht
definierten Auswertungsreihenfolge auftreten, aber das
ist ein anderes Thema -- die REIHENFOLGE hat ja nix damit
zu tun, dass alle Teile ÜBERHAUPT ausgewertet werden,
nur ist mit der verkürzten Auswertung nicht einmal das
garantiert.


> Mich haben jedenfalls seinerzeit die notwendig doppelt
> geschachtelten IF für Zeigervariablen immer gestört,
> weil man damit zwangsweise zwei Einrückungs-Niveaus
> gebraucht hat, von denen eins bis auf die zweite
> IF-Anweisung völlig leer war.

Ich sehe und verstehe Deinen Punkt, setze aber meine
Prioritäten etwas anders.

Ich will es mal so formulieren: Darüber, ob man eine
Kurzschrift für geschachtelte ifs braucht, will ich
(mangels Leidensdrucks bei mir) nicht streiten, das
nehme ich mal so hin.

Was ich aber VÖLLIG IDIOTISCH finde, das ist, dass diese
"Abkürzung für geschachtelte ifs" auf den ersten Blick
wie ein boolescher Operator aussieht!

von Jemand (Gast)


Lesenswert?

Egon D. schrieb:
> Was ich aber VÖLLIG IDIOTISCH finde, das ist, dass diese "Abkürzung für
> geschachtelte ifs" auf den ersten Blick wie ein boolescher Operator
> aussieht!

Ich persönlich einen deutlichen Unterschied zwischen && und ∧, ist 
vielleicht Staub auf deinem Monitor?
:^)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Egon D. schrieb:
> Funktionsaufrufe müssen nicht notwendig Seiteneffekte haben.

Wenn du das aber einschränken willst, dann dürften Funktionen keinen 
Kontext jenseits ihrer übergebenen Argumente benutzen.

"pure function" wäre das Stichwort. Ist halt nicht der gängige Standard 
für Funktionen in Programmiersprachen (wenngleich streng mathematische 
Funktionen genau so arbeiten), und man würde sich doch deutlich 
einschränken damit.

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


Lesenswert?

Egon D. schrieb:

> Boolesche Operatoren (also die "echten" booleschen
> Operatoren, die auch das machen, was die Algebra
> vorschreibt) sind kommutativ, d.h. a XOR b ist immer
> und unter allen Umständen dasselbe wie b XOR a.

Die Operatoren von C sind im Ergebnis auch kommutativ. Die Kurzschluß- 
Auswertung hat aber zwei relevante Vorzüge:

1. sie erlaubt, schnelleren Code zu produzieren. Weil der Compiler die 
Berechnung für && nach den ersten FALSE abbricht, kann der Programmierer 
die Bedingungen so anordnen, daß der Compiler möglichst selten alle 
Bedingungen überprüfen muß. Dito (negiert) für ||.

2. sie erlaubt, Tests zu schreiben, wo der eine vom Erfolg des anderen 
abhängt. Der Klassiker ist Stringverarbeitung mit Pointern, wo man den 
Nullpointer abfangen will. Beispiel:
1
void puts(char *p) 
2
{
3
  while (p && *p) {
4
    putc(*p++);
5
  }
6
}

Die Dereferenzierung von p darf nur für einen Pointer != 0 ausgeführt 
werden. Die Shortcut Regel erlaubt dem C-Programmierer, den Test des 
Pointers selber und den Test auf das Ziel des Pointers in einer 
Bedingung hinzuschreiben. Das mag hier unnütz erscheinen, aber wenn es 
bspw. um das Traversieren einer verlinkten Liste geht, wird das ein sehr 
nützliches Idiom.

> Für die STEUERSTRUKTUR, d.h. den Ablaufpfad des
> Programmes stimmt das auch für "&&" und "||" -- NICHT
> ABER für die Seiteneffekte. Das ist eine ziemlich
> bescheuerte Falle.

Du magst es für eine Falle halten. Ich halte solche Ausdrücke, wenn 
sie  Seiteneffekte haben, einfach nur für schlechten Stil. Ganz konkret 
ist das Beispiel des TO extrem schlechter Stil. Es ist aber nicht 
Aufgabe der Programmiersprache (des Sprachdesigns, des Sprachstandards) 
schlechten Stil zu verhindern. Im besten Fall funktioniert das einfach 
nur nicht, im schlechtesten Fall landet man bei einer 
Kindergartensprache wie Java, der man alle Features amputiert hat, die 
irgendwie falsch verwendet werden könnten.

Nur ein scharfes Messer ist ein gutes Messer. Wenn Ungeübte sich damit 
hin und wieder in den Finger schneiden, ist das kein Argument gegen 
scharfe Messer, sondern ein Argument für bessere Ausbildung.

> Dagegen löst "&" ja TATSÄCHLICH eine echte boolesche
> Verknüpfung der Operanden aus

Der & Operator ist vermutlich einer der am häufigsten mißverstandenen. 
Dabei ist das einfach nur ein Tribut an die Maschinennähe von C und soll 
die AND Operation der ALU auf Hochsprachenebene nutzbar machen. Auch 
hier ist die Verwendung von Teilausdrücken mit Seiteneffekten wieder 
extrem schlechter Stil. Möglicherweise sogar ein Bug, weil die 
Reihenfolge der Auswertung nicht definiert ist.

von Egon D. (Gast)


Lesenswert?

Jörg W. schrieb:

> Egon D. schrieb:
>> Funktionsaufrufe müssen nicht notwendig Seiteneffekte
>> haben.
>
> Wenn du das aber einschränken willst, dann dürften
> Funktionen keinen Kontext jenseits ihrer übergebenen
> Argumente benutzen.

Nee, ich glaube, wir missverstehen uns immer noch.

Ich habe nix dagegen, dass ein Sprachkonstrukt existiert,
das genau die Wirkung von "&&" hat.

Ich finde nur bescheuert, dass es
a) wie ein normaler boolescher Operator aussieht und
b) z.B. im K&R im Abschnitt "Vergleiche und logische
   Verknüpfungen" erklärt wird. Sinnvoll wäre die
   Erwähnung im Abschnitt "if...else" als spezielle
   Schreibweise für Bedingungen.

Man kann die Sprache selbst nicht ändern -- aber man kann
deutlich auf die "false friends" hinweisen.


> "pure function" wäre das Stichwort.

Danke für das Suchwort. Kannte ich noch nicht.

von (prx) A. K. (prx)


Lesenswert?

Egon D. schrieb:
> a) wie ein normaler boolescher Operator aussieht und

Nicht speziell auf diesen Fall bezogen, sondern auf das Gesamtbild: C 
minimiert reservierte Keywords zugunsten von Sonderzeichen. Grundlegende 
Sprachentscheidung. Ist damit ein Gegenmodell zu PL/I mit seinem grossen 
Vokabular nicht reservierter Keywords und komplexem Parser. Vgl 
einfachem Unix (C) und komplexem Multics (PL/I).

: Bearbeitet durch User
von Egon D. (Gast)


Lesenswert?

Axel S. schrieb:
1
> void puts(char *p)
2
> {
3
>   while (p && *p) {
4
>     putc(*p++);
5
>   }
6
> }

Ja... das ist natürlich eine harte Nuss.


Das Konstrukt
1
if (... && ...) { 
2
}

lässt sich immer noch umschreiben als
1
 
2
if (...) { 
3
  if (...) { 
4
  } 
5
}

Im Gegensatz zu Jörg finde ich das auch nicht
übermäßig weitschweifig.

Die while-Schleife wird schon deutlich sperriger,
wenn man sie ohne "&&" formulieren will:
1
while (p != NULL) { 
2
  if (*p == 0) { 
3
    break; 
4
  } else { 
5
    ... 
6
  } 
7
}


Hmm. Vielleicht geht auch
1
while (p != NULL) { 
2
  if (*p == 0) { break; } 
3
  ... 
4
}



>> Für die STEUERSTRUKTUR, d.h. den Ablaufpfad des
>> Programmes stimmt das auch für "&&" und "||" -- NICHT
>> ABER für die Seiteneffekte. Das ist eine ziemlich
>> bescheuerte Falle.
>
> Du magst es für eine Falle halten. Ich halte solche
> Ausdrücke, wenn sie  Seiteneffekte haben, einfach nur
> für schlechten Stil.

Das widerspricht sich nicht zwingend...


> Es ist aber nicht Aufgabe der Programmiersprache (des
> Sprachdesigns, des Sprachstandards) schlechten Stil
> zu verhindern.

Doch, zum Teil schon.

Guter oder schlechter Programmierstil ist, genauso wie
gute oder schlechte Lesbarkeit, für die Menschen wichtig,
nicht für den Compiler. Der Compiler hat kein Problem
damit, "hamptnquempftn" als Additionsoperator zu inter-
pretieren und "hamptnquampftn" als Funktionsaufruf für
den Neustart -- aber dem Menschen erweist man keinen
Dienst damit, wenn man solches Zeug in den Sprachstandard
schreibt.

Geschichten wie "=" vs. "==", "&" vs. "&&", prä- und post-
increment usw. gehören für mich in diese Schublade. Das
sind Fußangeln, die nur aus historischen Gründen in der
Sprache sind und auch bleiben müssen. Das könnte man heute
besser machen, wenn man die freie Wahl hätte.


> Nur ein scharfes Messer ist ein gutes Messer.

Ja.

Trotzdem sind Zangen für Elektriker abrutschsicher
gebaut, Hochspannungstastköpfe haben einen Handschutz,
Benutzer von Motorsägen können schnittfeste Kleidung
tragen, Drehmaschinen haben einen Futterschutz, der
bei Öffnen die Maschine stillsetzt und so weiter.

Berufsgenossenschaften und ähnliche Versicherungen sind
keine Freunde von "Darwin regelt" -- nur Programmierer
scheinen das Prinzip zu lieben...


> Wenn Ungeübte sich damit hin und wieder in den Finger
> schneiden, ist das kein Argument gegen scharfe Messer,
> sondern ein Argument für bessere Ausbildung.

Hier sind wir wieder einer Meinung.

Ich habe extra im K&R nachgesehen; dort wird "&&" und
"||" im Kapitel "Vergleiche und logische Verknüpfungen"
abgehandelt, was ich für einen didaktischen Fehler halte.

Sinnvoll wäre es im Kontext der Ablaufsteuerung, denn es
wird ja, soweit ich das übersehe, auch nur in diesem
Zusammenhang angewendet.


>> Dagegen löst "&" ja TATSÄCHLICH eine echte boolesche
>> Verknüpfung der Operanden aus
>
> Der & Operator ist vermutlich einer der am häufigsten
> mißverstandenen.

Naja, ich hätte es andersherum formuliert: "&&" ist
einer der am häufigsten missverstandenen Operatoren.

Letztlich gibt es ja drei Teilthemen:
1. boolesche Operationen auf "Binärvektoren" (Bitketten,
   Ganzzahlen,...)
2. boolesche Operationen auf booleschen Variablen,
3. Abfrage boolescher Variablen zum Zwecke der Ablauf-
   steuerung (Bedingungsprüfung).

"&", "|", "^" gehören zu Punkt 1. und müssen mMn nicht
weiter diskutiert werden.

Punkt 2. kann man im Kontext von C nicht sinnvoll
diskutieren, weil (ANSI-)C keine echten booleschen
Variablen kennt.

Zu Punkt 3. gehören "&&" und "||".

Es ist mMn didaktisch extrem ungeschickt, die Punkte 1.
und 3. gemeinsam zu lehren.

von (prx) A. K. (prx)


Lesenswert?

Egon D. schrieb:
> Das Konstrukt
> if (... && ...) {
> }
>
> lässt sich immer noch umschreiben als

> if (...) {
>   if (...) {
>   }
> }

Bau einen "else" Zweig ein und der Unterschied wird deutlich.

> Ich habe extra im K&R nachgesehen; dort wird "&&" und
> "||" im Kapitel "Vergleiche und logische Verknüpfungen"
> abgehandelt, was ich für einen didaktischen Fehler halte.

Es sind logische Verknüpfungen.

> Sinnvoll wäre es im Kontext der Ablaufsteuerung, denn es
> wird ja, soweit ich das übersehe, auch nur in diesem
> Zusammenhang angewendet.

Eben nicht, das ist ja gerade der Punkt:
  b = p && *p == 99;
Kommt nicht so oft vor, aber illustriert, dass es sich dabei wirklich um 
logische Operatoren handelt, nicht um Ablaufkonstrukte wie if/while.

> Naja, ich hätte es andersherum formuliert: "&&" ist
> einer der am häufigsten missverstandenen Operatoren.

Wie eigentlich immer bei extrapolierten Bauchgefühlen gehen die 
Meinungen auseinander.

Für blutige Anfänger offensichtlicher wäre sowas wie
  and / or
für binäre Operatoren und
  andif / orif
für logische Operatoren gewesen, aber das ist eben der Sprachphilosophie 
geschuldet, Operatoren statt Keywords zu verwenden. Mehr als optisches 
Gimmik ist das zudem nicht.

: Bearbeitet durch User
von Egon D. (Gast)


Lesenswert?

A. K. schrieb:

> Egon D. schrieb:
>> a) wie ein normaler boolescher Operator aussieht und
>
> Nicht speziell auf diesen Fall bezogen, sondern auf
> das Gesamtbild: C minimiert reservierte Keywords
> zugunsten von Sonderzeichen.

Ja. Ist mir (inzwischen) klar.


> Grundlegende Sprachentscheidung.

Hmmm... ich würde eher sagen: Historisch notwendiges
Implementierungsdetail.

Dank des Präprozessors kann ich mir "INCR" für "++"
und "DECR" für "--" definieren, "AND" für "&&", "OR"
für "||" und so weiter...

(Man könnte auch einfach einen neueren C-Standard
verwenden, der zahlreiche solche Makros enthält, aber
das wäre ja unsportlich... :)


Als "grundlegende Sprachentscheidung" würde ich eher
den Präprozessor bezeichnen (den z.B. Pascal nicht
hat), oder die Verlagerung der I/O-Funktionen in die
Standardbibliothek (was Pascal fehlerhafterweise auch
nicht macht), oder die Möglichkeit, C-Programme auch
"freestanding" einzusetzen (was Pascal auch nicht so
einfach kann).

von Egon D. (Gast)


Lesenswert?

A. K. schrieb:

> Egon D. schrieb:
>> Das Konstrukt
>> if (... && ...) {
>> }
>>
>> lässt sich immer noch umschreiben als
>
>> if (...) {
>>   if (...) {
>>   }
>> }
>
> Bau einen "else" Zweig ein und der Unterschied
> wird deutlich.

Hübsch :)

Nur fürs Protokoll: Ich hatte nicht behauptet, dass
beides in jeder Hinsicht GLEICHWERTIG sei. Ich sprach
von "Umschreiben".


>> Ich habe extra im K&R nachgesehen; dort wird "&&"
>> und "||" im Kapitel "Vergleiche und logische
>> Verknüpfungen" abgehandelt, was ich für einen
>> didaktischen Fehler halte.
>
> Es sind logische Verknüpfungen.

Das war nicht mein Punkt.

Es gibt keinerlei Zwang -- und zwar weder sachlichen
noch didaktischen --, alle logischen Operatoren in
einem Kapitel abzuhandeln.
Bratfett und Getriebefett stehen ja auch nicht im
selben Regal, auch wenn es sich unzweifelhaft in beiden
Fällen um Fette handelt. Die typische Verwendung ist
halt verschieden...


>> Sinnvoll wäre es im Kontext der Ablaufsteuerung,
>> denn es wird ja, soweit ich das übersehe, auch
>> nur in diesem Zusammenhang angewendet.
>
> Eben nicht, das ist ja gerade der Punkt:
>   b = p && *p == 99;
> Kommt nicht so oft vor,

Eben. DAS ist der Punkt.


> aber illustriert, dass es sich dabei wirklich um
> logische Operatoren handelt, nicht um Ablaufkonstrukte
> wie if/while.

Missverständnis. Es geht mir nicht um die syntaktische,
die compilertechnische Seite -- die ist nur Ausdruck
des Sparzwanges, unter dem K&R leiden mussten.

Meine Aussage war: In der Programmierpraxis wird man,
wenn man einen einigermaßen sauberen Programmierstil
pflegt, Ausdrücke, die "&&" bzw. "||" enthalten, über-
wiegend zur Ablaufsteuerung VERWENDEN. (Und meiner
Meinung nach sollte man Anfänger auch von vornherein
dazu erziehen, dies zu tun.)

Das hat aus meiner Sicht einfach damit zu tun, dass C
nicht über einen echten booleschen Datentyp verfügt.
Daher sind boolesche Ausdrücke, die den berechneten
Wahrheitswert einer booleschen Variablen zuweisen,
immer mehr oder weniger Krampf, und ich finde, man sollte
das unterlassen, wenn immer möglich.
Das sauberste ist daher, die Bedingung auszuwerten
und sofort in einer Anweisung zur Ablaufsteuerung zu
verwenden, ohne sie irgend einer Variablen zuzuweisen.

Damit sind wir bei meiner Aussage von oben.

Letztlich verstehe ich das auch als den Kern der Kritik
von W.S.: Es mag ja wohl sein, dass der Compiler bzw.
der Sprachstandard manches syntaktisch nicht scharf
trennt, was man inhaltlich besser trennen sollte (z.B.
Ganzzahlen und einzelne Wahrheitswerte.)
Nur -- der denkende Mensch ist ja nicht denselben
Beschränkungen unterworfen wie der Compiler, also
warum trennt man diese Dinge nicht wenigstens im
Denken?


> Für blutige Anfänger offensichtlicher wäre sowas wie
>   and / or
> für binäre Operatoren und
>   andif / orif
> für logische Operatoren gewesen, aber das ist eben
> der Sprachphilosophie geschuldet, Operatoren statt
> Keywords zu verwenden.

Nein, das ist zu kurz gegriffen. Die Schlüsselworte
könnte ich mir auch privat per Präprozesser herstellen.

Nein, mein Punkt ist ein anderer.

Die Bitoperationen "&", "|", "^" operieren in
natürlicher Weise über Binärvektoren (Bitketten,
Ganzzahlen, ...) und liefern solche auch als
Ergebnisse zurück, die man ohne Bauchschmerzen wieder
in passenden Variablen speichern kann.
(Mit "passend" ist nicht nur gemeint: "Der Compiler
meckert nicht", sondern "das ist inhaltlich passend".)

Nun kennt C aber keinen echten booleschen Datentyp,
also einen Datentyp, der für einzelne Wahrheitswerte
-- und NUR für einzelne Wahrheitswerte -- vorgesehen
ist und vom Compiler auch in dieser Weise behandelt
und geprüft wird.

Es gibt aber zahlreiche Operatoren, die einzelne
Wahrheitswerte liefern (alle Vergleiche) oder verknüpfen
("&&", "||"). Diese DARF man natürlich rein syntaktisch
in ganzzahligen Variablen zwischenspeichern, aber
semantisch passend ist das nicht -- sondern genau das,
was ich weiter oben als "Krampf" bezeichnet habe.

Anders gesagt: Alle logischen Verknüpfungen werden in
Ausdrücken verwendet.

Einige dieser Ausdrücke liefern Ergebnisse, die INHALTLICH
als Ganzzahlen oder Binärvektoren zu interpretieren sind.
Bei diesen Ausdrücken ist es auch INHALTLICH angemessen,
deren Ergebnisse einer entsprechenden Variablen zuzuweisen
und dort zu speichern. (Das sind "arithmetische Ausdrücke"
im weitesten Sinne).

Andere Ausdrücke liefern Ergebnisse, die INHALTLICH als
Wahrheitswert zu interpretieren sind. Es gibt aber
keinen Datentyp, der INHALTLICH AUSSCHLIESZLICH für das
Speichern von Wahrheitswerten vorgesehen ist (-- auch
wenn es FORMAL, d.h. syntaktisch erlaubt ist, dafür
Integers heranzuziehen.)
Darüberhinaus kenne ich aus der Programmierpraxis auch
keine Anwendung, bei der Notwendigkeit besteht, größere
Mengen EINZELNER Wahrheitswerte zu speichern. Wenn viele
binäre Variablen im Spiel sind, arbeitet man in der Regel
mit ganzen Binärvektoren; das fällt dann faktisch wieder
in das Kapitel "spezielle Arithmetik".

Operatoren, die einzelne Wahrheitswerte erzeugen oder
verknüpfen, DIENEN ganz offensichtlich INHALTLICH der
Ablaufsteuerung (auch wenn es syntaktisch logische
Operatoren sind).
Man sollte sie deswegen auch im Kontext der Ablauf-
steuerung besprechen und erklären.


> Mehr als optisches Gimmik ist das zudem nicht.

Das sehe ich anders. Es ist eine Frage der Klarheit
im Denken.

von (prx) A. K. (prx)


Lesenswert?

Egon D. schrieb:
> Darüberhinaus kenne ich aus der Programmierpraxis auch
> keine Anwendung, bei der Notwendigkeit besteht, größere
> Mengen EINZELNER Wahrheitswerte zu speichern.

Und das in einem Mikrocontroller Forum ;-). Ein Register mit 32 
"Interrupt Pending" Bits ist genau das, ebenso Status- oder 
Steuerregister von I/O Modulen wie einer UART.

: Bearbeitet durch User
von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

A. K. schrieb:
> Für blutige Anfänger offensichtlicher wäre sowas wie
>   and / or
> für binäre Operatoren und
>   andif / orif
> für logische Operatoren gewesen

Aber genau das gibt's doch. In C über den Header iso646.h, in C++ fest 
eingebaut. Nennt sich dann "and", "bitand", "or", "bitor", ...

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

Über die Semantik von "&&" zu diskutieren ist auch eher müßig. In jedem 
C(++)-Buch wird darauf hingewiesen, und es geht in Fleisch und Blut 
über. C ist keine Anfängersprache, und wer das unbedingt als 1. Sprache 
("blutiger Anfänger") lernen möchte ist schon selbst schuld.

C hat noch ganz andere Probleme die viel einschneidender sind als die 
schnöde Short-Circuit-Evaluation...

von Mark (Gast)


Lesenswert?

Jörg W. schrieb:
>  (Willst du wirklich "&&"?)
>
Ich weiss, worauf du hinaus willst. Wenn ich & verwende, wird bitweise 
und-verknüpft und alles ausgewertet.

Aber && funktioniert doch auch, wenn alle Bits gesetzt sein müssen.

> if ((__HAL_ADC_GET_FLAG(ADC, flag1) &&
>     (__HAL_ADC_GET_FLAG(ADC, flag2) &&
>     (__HAL_ADC_GET_FLAG(ADC, flag3) &&
>     (__HAL_ADC_GET_FLAG(ADC, flag4)) ...

Bei der Bedingung ist es doch so, dass wenn flag 1 nicht gesetzt ist, 
alle anderen flags gar nicht mehr überprüft werden.
Ist flag1 gesetzt, wird 2 geprüft, ist es auch gesetzt wird 3 geprüft, 
wenn dieser 0 ist, dann springt das Programm raus.

von Bernd K. (prof7bit)


Lesenswert?

Egon D. schrieb:
> Ich habe nix dagegen, dass ein Sprachkonstrukt existiert,
> das genau die Wirkung von "&&" hat.
>
> Ich finde nur bescheuert, dass es
> a) wie ein normaler boolescher Operator aussieht

Weil es einer ist vielleicht, und auch aufs i-Tüpfelchen genau so 
funktioniert wie ein boolesches UND?

von Rolf M. (rmagnus)


Lesenswert?

Jörg W. schrieb:
> Nach Überfliegen des Standards tendiere ich dazu, die Erklärung auf
> Stackoverflow für korrekt zu befinden.

Ich würde auch davon ausgehen, dass - wenn es nicht ausdrücklich erlaubt 
ist, Seiteneffekte wegzulassen - diese immer auch ausgeführt werden 
müssen.

Luther B. schrieb:
> foo() & i++ kann bedeuten
> t0=foo()
> t1=i
> i=i+1
> => t0 & t1
>
> oder
> t1=i
> t0=foo()
> i=i+1
> => t0 & t1
>
> oder
> t1=i
> i=i+1
> t0=foo()
> => t0 & t1
>
> Sollte foo() i lesen oder schreiben, dann kan man jeweils ein anderes
> Ergebnis und/oder einen anderen Wert für i erhalten.

Es kann auch noch ganz andere Sachen bedeuten. Zum Beispiel ist es nicht 
ungewöhnlich, dass Prozessoren heutzutage mehrere aufeinanderfolgende 
Anweisungen parallelisieren können. Dann wird evtl. der Inhalt von foo() 
und das i++ gleichzeitig auf separaten Pipelines ausgeführt. Dann gibt 
es eine Race-Condition, und es hängt sozusagen von der Mondphase ab, wer 
gewinnt. Oder es gibt eine Hardware-Exception, weil der Prozessor nicht 
weiß, wie er auf dem selben Register zwei Aktionen gleichzeitig 
durchführen soll, und der Prozess wird terminiert. Undefined Behaviour 
ist mehr als nur eine vom Compiler abhängige Reihenfolge der Ausführung.

Cyblord -. schrieb:
> Mark schrieb:
>> wenn ihr sagt Standard lesen, wo lest ihr das nach?
>> Wo finde ich den richtigen C-Standard?
>
> Es gibt nicht "den richtigen C-Standard". C99 wird oft verwendet. Ist
> aber kein muss. Es gibt noch C89 z.B.

Mit Erscheinen von C99 wurde C89 für ungültig erklärt. Es wird zwar noch 
verwendet, aber eigentlich gibt es das offiziell nicht mehr.

Egon D. schrieb:
>> und da reden wir jetzt nicht nur über sowas wie ++ oder
>> --, sondern eben auch darüber, dass man dann dann auch
>> keine Funktionsaufrufe mehr als Operanden haben dürfte.
>
> Hmm. Nee. Einspruch.
> Funktionsaufrufe müssen nicht notwendig Seiteneffekte
> haben. Wenn in dem booleschen Ausdruck nur gutartige
> Funktionen aufgerufen werden, die nur lesen, rechnen und
> das Ergebnis zurückliefern, müsste alles in Butter sein.

Dann spielt es aber auch keine Rolle, ob eine Short Circuit Evaluation 
passiert oder nicht. Diese wirkt sich ja nur auf die Seiteneffekte (die 
übrigens auch eigentlich eine Falschübersetzung von "side effects" ist) 
aus.

Egon D. schrieb:
> Dank des Präprozessors kann ich mir "INCR" für "++"
> und "DECR" für "--" definieren, "AND" für "&&", "OR"
> für "||" und so weiter...
>
> (Man könnte auch einfach einen neueren C-Standard
> verwenden, der zahlreiche solche Makros enthält, aber
> das wäre ja unsportlich... :)

Ja, da müsste man ja auf den furchtbar neuen von vor 20 Jahren gehen.

Egon D. schrieb:
> Nun kennt C aber keinen echten booleschen Datentyp,
> also einen Datentyp, der für einzelne Wahrheitswerte
> -- und NUR für einzelne Wahrheitswerte -- vorgesehen
> ist und vom Compiler auch in dieser Weise behandelt
> und geprüft wird.

Doch, kennt es. Aber ebenfalls "erst" seit 20 Jahren.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Rolf M. schrieb:
> Dann wird evtl. der Inhalt von foo()
> und das i++ gleichzeitig auf separaten Pipelines ausgeführt. Dann gibt
> es eine Race-Condition, und es hängt sozusagen von der Mondphase ab, wer
> gewinnt. Oder es gibt eine Hardware-Exception, weil der Prozessor nicht
> weiß, wie er auf dem selben Register zwei Aktionen gleichzeitig
> durchführen soll

Könntest du dafür ein Beispiel zeigen? Ein Stück Assembler-Code, dessen 
Ergebnis aufgrund so einer solchen Race Condition nicht eindeutig ist 
und welches eine Exception verursachen kann?

von (prx) A. K. (prx)


Lesenswert?

Rolf M. schrieb:
> Es kann auch noch ganz andere Sachen bedeuten. Zum Beispiel ist es nicht
> ungewöhnlich, dass Prozessoren heutzutage mehrere aufeinanderfolgende
> Anweisungen parallelisieren können. Dann wird evtl. der Inhalt von foo()
> und das i++ gleichzeitig auf separaten Pipelines ausgeführt. Dann gibt
> es eine Race-Condition, und es hängt sozusagen von der Mondphase ab, wer
> gewinnt.

M.W. definiert die Architektur entweder, dass die formelle Reihenfolge 
der Operationen immer dort gewinnt, wo es Konflikte geben könnte, 
unabhängig davon wie superskalar die Implementierung ist. Oder es ist 
definiert, dass innerhalb einer solchen Befehlsguppe keine Konflikte 
geben darf.

von Bernd K. (prof7bit)


Lesenswert?

Rolf M. schrieb:
> Ich würde auch davon ausgehen, dass - wenn es nicht ausdrücklich erlaubt
> ist, Seiteneffekte wegzulassen - diese immer auch ausgeführt werden
> müssen.

Ich würde gerade bei einer Sprache wie C in der der Compiler alle 
Freiheiten hat und nichts zwingend funktioniert was nicht explizit 
garantiert ist genau das Gegenteil erwarten, nämlich daß er auch an 
dieser Stelle die naheliegende Optimierung durchführen darf (und in 
diesem Falle sogar ganz dem Charakter der Sprache entsprechend die 
Optimierung garantiert immer durchführt).

Und auch bei anderen Sprachen würde ich mich nicht unbedingt darauf 
verlassen daß Ausdrücke ausgewertet werden deren Ergebnis überhaupt 
nicht benötigt wird, zumindest so lange nicht bis mir die Dokumentation 
das hoch und heilig verspricht.

Immerhin ist der Hauptzweck eines Ausdrucks der daß er den korrekten 
Wert liefert, nicht daß man da noch irgendwelche Seiteneffekte drin 
versteckt die dann notgedrungen von irgendwelchen Auswerteregeln 
abhängen, deshalb heißt es auch "Ausdruck" und nicht "Anweisung" oder 
gar "Kontrollstruktur". Und ja: ich halte mittlerweile (foo && 
seiteneffekt()) für schlechten Stil, auch wenn es in manchen Sprachen 
klar definierte Garantien über deren Verhalten gibt.

Für mich ist && in erster Linie ein boolescher Operator und ich verwende 
ihn tunlichst nur als solchen und für bedingte Ausführung von 
Seiteneffekten verwende ich lieber die explizit dafür gedachten 
Kontrollstrukturen, das ist auch einfacher zu lesen.

: Bearbeitet durch User
von Mark (Gast)


Lesenswert?

Mark schrieb:
> Jörg W. schrieb:
>>  (Willst du wirklich "&&"?)

>
>> if ((__HAL_ADC_GET_FLAG(ADC, flag1) &&
>>     (__HAL_ADC_GET_FLAG(ADC, flag2) &&
>>     (__HAL_ADC_GET_FLAG(ADC, flag3) &&
>>     (__HAL_ADC_GET_FLAG(ADC, flag4)) ...
>
> Bei der Bedingung ist es doch so, dass wenn flag 1 nicht gesetzt ist,
> alle anderen flags gar nicht mehr überprüft werden.
> Ist flag1 gesetzt, wird 2 geprüft, ist es auch gesetzt wird 3 geprüft,
> wenn dieser 0 ist, dann springt das Programm raus.

Meine Frage interessiert wohl niemanden mehr :(

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Mark schrieb:
> Mark schrieb:
>> Jörg W. schrieb:
>>>  (Willst du wirklich "&&"?)
>
>>
>>> if ((__HAL_ADC_GET_FLAG(ADC, flag1) &&
>>>     (__HAL_ADC_GET_FLAG(ADC, flag2) &&
>>>     (__HAL_ADC_GET_FLAG(ADC, flag3) &&
>>>     (__HAL_ADC_GET_FLAG(ADC, flag4)) ...
>>
>> Bei der Bedingung ist es doch so, dass wenn flag 1 nicht gesetzt ist,
>> alle anderen flags gar nicht mehr überprüft werden.
>> Ist flag1 gesetzt, wird 2 geprüft, ist es auch gesetzt wird 3 geprüft,
>> wenn dieser 0 ist, dann springt das Programm raus.
>
> Meine Frage interessiert wohl niemanden mehr :(

Deine Interpretation stimmt schon, ist nur in dieser Form nicht sehr 
nützlich.

Meine ursprüngliche Frage war, ob du tatsächlich alle Bits gesetzt 
haben willst für die Bedingung, das hattest du bejaht. (Ich kenne selbst 
halt eher die Fälle, bei denen man mit irgendeinem Bit weitergemacht 
hätte.)

„Nicht sehr nützlich“ deshalb, weil du dir so von der Hardware bis zu 
viermal den kompletten Registerinhalt abholen gehst, nur um jeweils ein 
Bit zu testen. Stattdessen kannst du mit weniger Aufwand halt den einmal 
geholten Inhalt komplett auf alle Bits gleichzeitig testen.

von Mark (Gast)


Lesenswert?

Jörg W. schrieb:
> Mark schrieb:
>> Mark schrieb:
>>> Jörg W. schrieb:


>
> Deine Interpretation stimmt schon, ist nur in dieser Form nicht sehr
> nützlich.
>
> Meine ursprüngliche Frage war, ob du tatsächlich alle Bits gesetzt
> haben willst für die Bedingung, das hattest du bejaht. (Ich kenne selbst
> halt eher die Fälle, bei denen man mit irgendeinem Bit weitergemacht
> hätte.)
>
> „Nicht sehr nützlich“ deshalb, weil du dir so von der Hardware bis zu
> viermal den kompletten Registerinhalt abholen gehst, nur um jeweils ein
> Bit zu testen. Stattdessen kannst du mit weniger Aufwand halt den einmal
> geholten Inhalt komplett auf alle Bits gleichzeitig testen.

danke

von Bernd K. (prof7bit)


Lesenswert?

Jörg W. schrieb:
> „Nicht sehr nützlich“ deshalb, weil du dir so von der Hardware bis zu
> viermal den kompletten Registerinhalt abholen gehst

Kann man der HAL_ADC_GET_FLAG() nicht gleich die ganze Bitmaske mit 
mehreren Flags gleichzeitig übergeben? Ich meine so eine Verwendung mal 
irgendwo gesehen zu haben, hab aber diese HAL gerade nicht zur Hand also 
kann ich nicht reinschauen.

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


Lesenswert?

Egon D. schrieb:
>
> Das Konstrukt
>
1
> if (... && ...) {
2
> }
3
>
>
> lässt sich immer noch umschreiben als
>
1
> if (...) {
2
>   if (...) {
3
>   }
4
> }
5
>
>
> Im Gegensatz zu Jörg finde ich das auch nicht
> übermäßig weitschweifig.

Dann bau eine dritte und vierte Bedingung ein. Mische && und ||. 
Verwende Klammern. Füge Negation hinzu. Ist die algebraische Notation 
mit && und || immer noch nicht verständlicher?

> Die while-Schleife wird schon deutlich sperriger,
> wenn man sie ohne "&&" formulieren will:
>
1
> while (p != NULL) {
2
>   if (*p == 0) {
3
>     break;
4
>   } else {
5
>     ...
6
>   }
7
> }
8
>

Eigentlich wöllte man das eher so:
1
void puts(char *p)
2
{
3
  if (p == 0) 
4
    return;
5
  while (*p)
6
    puts(*p++);
7
}

Aber da meckern die Apologeten der strukturierten Programmierung wieder, 
daß es zwei Ausgänge aus der Funktion gibt. Also:
1
void puts(char *p)
2
{
3
  if (p != 0) {
4
    while (*p)
5
      puts(*p++);
6
  }
7
}

Hat wieder den Nachteil, eine Einrückungsebene zu verschwenden.

> Hmm. Vielleicht geht auch
>
1
> while (p != NULL) {
2
>   if (*p == 0) { break; }
3
>   ...
4
> }
5
>

Konstrukte mit break finde ich immer schwieriger zu lesen als solche 
ohne. An allen deinen Varianten mißfällt mir, daß die eigentliche 
Abbruchbedingung (Ende des Strings erreicht) gar nicht in der 
Schleifenbedingung steht. Da steht der Test auf den Nullpointer, der da 
logisch eigentlich gar nichts verloren hat. Die Schleife iteriert über 
die Zeichen des Strings. Die Abbruchbedingung ist, das terminierende 
0-Zeichen erreicht zu haben.

Der Test auf den Nullpointer steht da nur, um das API der Funktion zu 
vereinfachen. Wahlweise, weil man den Nullpointer explizit zulassen will 
oder weil man fehlertolerant sein will.

>> Es ist aber nicht Aufgabe der Programmiersprache (des
>> Sprachdesigns, des Sprachstandards) schlechten Stil
>> zu verhindern.
>
> Doch, zum Teil schon.

Wie gesagt: das funktioniert nicht. Schlechte Programmierer schreiben in 
jeder Programmiersprache schlechten Code. Ich persönlich vertrete eher 
die gegenteilige Ansicht: statt es schwer zu machen, schlechten Code zu 
schreiben, sollte man es leicht machen, guten (lesbaren, wartbaren, 
schnellen) Code zu schreiben. An dieser Stelle ist C ganz sicher nicht 
der Weisheit letzter Schluß. Aber das ist einfach der Historie 
geschuldet. Als C erfunden wurde, hatten Computer nur einige Dutzed 
Kilobyte RAM. Da mußte man den Compiler klein halten.

> Geschichten wie "=" vs. "==", "&" vs. "&&", prä- und post-
> increment usw. gehören für mich in diese Schublade. Das
> sind Fußangeln, die nur aus historischen Gründen in der
> Sprache sind und auch bleiben müssen. Das könnte man heute
> besser machen, wenn man die freie Wahl hätte.

Macht man ja auch. Kuck dir doch den Zoo an Programmiersprachen an, den 
wir heute haben. Aber an C kann und will man nichts mehr ändern. Sobald 
man das macht, verliert man den größten (und einzigen?) Vorteil von C: 
daß man Milliarden Zeilen existierenden Code weiterverwenden kann.

> Ich habe extra im K&R nachgesehen; dort wird "&&" und
> "||" im Kapitel "Vergleiche und logische Verknüpfungen"
> abgehandelt, was ich für einen didaktischen Fehler halte.
>
> Sinnvoll wäre es im Kontext der Ablaufsteuerung

Nein. Das sind logische Operatoren.

> denn es wird ja, soweit ich das übersehe, auch nur in diesem
> Zusammenhang angewendet.

Dieser Satz wird durch das Wörtchen "nur" komplett falsch. Ohne "nur" 
könnte man ihn gelten lassen.

von Egon D. (Gast)


Lesenswert?

A. K. schrieb:

> Egon D. schrieb:
>> Darüberhinaus kenne ich aus der Programmierpraxis
>> auch keine Anwendung, bei der Notwendigkeit besteht,
>> größere Mengen EINZELNER Wahrheitswerte zu speichern.
>
> Und das in einem Mikrocontroller Forum ;-).

Jaja... tut mir leid :)


> Ein Register mit 32 "Interrupt Pending" Bits ist
> genau das, ebenso Status- oder Steuerregister von
> I/O Modulen wie einer UART.

Hmm.
Eigentlich nicht, finde ich.

Kern meiner länglichen Argumentation war ja die
Unterscheidung zwischen "Bits" und "Wahrheitswerten".

Du magst die Begriffe (vielleicht mit Recht) als
schlecht gewählt kritisieren, aber inhaltlich war
der Kernpunkt ja nur der, dass sich gewöhnliche
Bitverknüpfungen als spezielle Arithmetik auffassen
lassen, bei der Bitvektoren gelesen, verknüpft und
wieder in Variablen geschrieben werden. Bits sind
ganz gewöhnliche Anwendungsdaten, die mit
arithmetischen oder logischen Operatoren verknüpft
und in Variablen gespeichert werden können.

Demgegenüber bilden die "Wahrheitswerte" das
Bindeglied zwischen der ALU und dem Steuerwerk. Der
"Wahrheitswert" wird an einer bestimmten Stelle im
Programmablauf berechnet und gilt auch nur für genau
diesen gerade aktuellen Durchlauf; er wandert letztlich
i.d.R. in ein Flag und beeinflusst von dort aus einen
bedingten Sprung oder eine sonstige bedingte Befehls-
ausführung.

In dieser Sichtweise sind Statusbits, die von externer
Hardware gesetzt werden, erstmal nur "Bits".
Zu "Wahrheitswerten" werden sie erst, wenn sie mittels
einer passenden Abfrage in ein Flag wandern und den
Programmablauf beeinflussen.

von Egon D. (Gast)


Lesenswert?

Niklas G. schrieb:

> Über die Semantik von "&&" zu diskutieren ist
> auch eher müßig.

Es ging ja nicht (primär nur) um die Semantik,
sondern auch um Didaktik.


> In jedem C(++)-Buch wird darauf hingewiesen, und
> es geht in Fleisch und Blut über.

Ich habe den K&R vorliegen. Als historisches Dokument
finde ich ihn sehr interessant, aber als Leitfaden
für das Erlernen von C finde ich ihn untauglich.

Von "gut strukturiert" kann m.E. absolut keine Rede
sein.


> C ist keine Anfängersprache, und wer das unbedingt
> als 1. Sprache ("blutiger Anfänger") lernen möchte
> ist schon selbst schuld.

???
C ist nicht meine erste Programmiersprache.


> C hat noch ganz andere Probleme die viel einschneidender
> sind als die schnöde Short-Circuit-Evaluation...

Lass' mal bitte hören. Ich bin immer auf der Suche nach
Futter.

(Ach so -- keine Angst: Ich verspreche, hier KEINE
Diskussion darüber anzufangen :)

von Egon D. (Gast)


Lesenswert?

Axel S. schrieb:

>> Im Gegensatz zu Jörg finde ich das auch nicht
>> übermäßig weitschweifig.
>
> Dann bau eine dritte und vierte Bedingung ein.
> Mische && und ||. Verwende Klammern. Füge Negation
> hinzu. Ist die algebraische Notation mit &&
> und || immer noch nicht verständlicher?

Naja... unsere Sichtweisen unterscheiden sich halt
schon im Grundansatz. Ich habe es generell lieber,
wenn komplizierte Bedingungen in mehrere Einzel-
entscheidungen zerlegt notiert werden. Da erkenne
ich dann einigermaßen schnell, WAS überhaupt
passiert.

Zu verstehen, warum das, was passiert, sachlich
RICHTIG ist, ist dann nochmal ein anderes Thema.


[...]
> void puts(char *p)
> {
>   if (p != 0) {
>     while (*p)
>       puts(*p++);
>   }
> }
>
> Hat wieder den Nachteil, eine Einrückungsebene zu
> verschwenden.

Das stört mich nicht. Unschön finde ich aber, dass
diese Varianten nicht wirklich äquivalent zu Deiner
ursprünglichen Formulierung ist, weil hier der Test
(p != 0) nur ein einziges Mal ausgeführt wird -- und
nicht bei jedem Schleifendurchlauf, wie im Original.

Abgesehen davon ist die Idee nicht schlecht.


>> Hmm. Vielleicht geht auch
>>> while (p != NULL) {
>>   if (*p == 0) { break; }
>>   ...
>> }
>>
> Konstrukte mit break finde ich immer schwieriger zu
> lesen als solche ohne. An allen deinen Varianten
> mißfällt mir, daß die eigentliche Abbruchbedingung
> (Ende des Strings erreicht) gar nicht in der
> Schleifenbedingung steht.

Naja, ich habe lediglich versucht, eine zur originalen
Formulierung äquivalente Schreibweise zu finden. Das
sollte auch erfüllt sein, denn es werden in jedem
vollständigen Durchlauf beide Bedingungen geprüft, die
Bedingungen werden immer in der richtigen Reihenfolge
geprüft, und die zweite Bedingung wird NICHT geprüft,
wenn schon die erste nicht erfüllt war. Sollte also
exakt äquivalent zur Ursprungsvariante sein.

Natürlich kann man das scheußlich finden, das steht
jedem frei.


> Ich persönlich vertrete eher die gegenteilige Ansicht:
> statt es schwer zu machen, schlechten Code zu schreiben,
> sollte man es leicht machen, guten (lesbaren, wartbaren,
> schnellen) Code zu schreiben. [...]

Gut, darauf können wir uns einigen.


>> Geschichten wie "=" vs. "==", "&" vs. "&&", prä- und
>> postincrement usw. gehören für mich in diese Schublade.
>> Das sind Fußangeln, die nur aus historischen Gründen
>> in der Sprache sind und auch bleiben müssen. Das könnte
>> man heute besser machen, wenn man die freie Wahl hätte.
>
> Macht man ja auch. [...] Aber an C kann und will man
> nichts mehr ändern.

Klar.
Deswegen ja auch schon in meiner allerersten Antwort an
W.S. meine Aussage: An der Sprache SELBST kann man heute
nix mehr ändern -- wohl aber am Umgang mit ihr.


>> Ich habe extra im K&R nachgesehen; dort wird "&&" und
>> "||" im Kapitel "Vergleiche und logische Verknüpfungen"
>> abgehandelt, was ich für einen didaktischen Fehler halte.
>>
>> Sinnvoll wäre es im Kontext der Ablaufsteuerung
>
> Nein.

Mit Verlaub: Doch.


> Das sind logische Operatoren.

Das habe ich auch nicht bestritten. Sie dienen aber
unterschiedlichen Zwecken -- und deshalb sollte man
sie nicht zusammen lehren.

Details dazu in meiner überlangen Antwort an A.K.;
ich möchte das nicht alles hier wiederholen.

von Theor (Gast)


Lesenswert?

Egon D. schrieb:
> [...]

> Kern meiner länglichen Argumentation war ja die
> Unterscheidung zwischen "Bits" und "Wahrheitswerten".
> [...]

Diese Unterscheidung beruht meiner Ansicht nach auf einem 
Missverständnis.

"Bit" bezeichnet zunächst nur eine (Informations-)Einheit.
Ein "Wahrheitswert" aber ist eine Interpretation eines Zustandes - genau 
wie ein Integerwert es ist.

Auch teile ich Deine Ansicht nicht, dass Wahrheitswerte (wenn ich Deine 
Wortwahl korrekt nachvollziehe) "natürlicherweise" den Programmfluss 
steuern, während das Vektoren von Bits ebenso natürlicherweise nicht 
tun.

Diese begriffliche Trennung zwischen Ablauf und Berechnung gab es noch 
vor den Programmiersprachen. Realisiert in Maschinen ist sie aber 
"künstlich". Das kann man feststellen, wenn man auf Fälle stösst, in 
denen ein Ablauf durch eine Abfolge von Bitvektoroperationen ersetzt 
wird (oder umgekehrt). [Ein Beispiel dafür ist, die hier öfter mal 
auftauchende Frage, nach der Ermittlung des höchstwertigen gesetzten 
Bits, bei der zunächst ein ablaufzentrierte Version vorliegt - also 
eine, bei der in exzessivem Ausmaß Bedingungen geprüft werden. Siehe 
https://graphics.stanford.edu/~seander/bithacks.html. ]

Wenn die Trennung willkürlich ist und der überkommenen Denkweise 
geschuldet, dann ist sie es folgerichtig auch in der Didaktik der 
Programmiersprachen.

"Natürlich" jedenfalls, im Sinne von "inherent" ist das keinesfalls.

Es ist schon folgerichtig, dass die Operationen "&&" usw. ihre Operanden 
als Zweiwertig betrachten, in dem sie 0 als den Repräsentanten von 
Falsch und alle Werte ungleich 0 als die anderen Repräsentanten von Wahr 
betrachten.
Damit sind das de fakto boolsche Operatoren und nichts anderes.
Und damit sind sie ganz sinnvoll auch Subjekt von 
Kurzschluss-Auswertungen.

In dem Zusammenhang verweist Du auf Funktionen mit Seiteneffekten.

Aber das ist letztlich wieder eine Frage der tradierten Denkweise. Wie 
Du richtig sagst, gibt es in der reinen boolschen Logik, keine 
Seiteneffekt. Aber da gibt es auch keine Funktionen. Andererseits wirst 
Du einen länglichen disjunktiven Ausdruck nicht weiter auswerten, wenn 
einer der Werte 0 resp. Falsch ist.

Ich möchte Dir zugeben, dass es sinnvoll sein könnte, dass Thema in 
beiden Zusammenhängen jeweils zu behandeln. Nicht jedoch, es statt bei 
den Operation, bei den bedingten Anweisungen zu behandeln.

Es gäbe noch mehr zu Deinen Argumenten zu sagen. Aber es ist spät und 
das wird vielleicht eine längliche Diskussion. Ich denke aber, ich 
verstehe, was Du meinst und worauf Du hinaus willst.


Was mich "anzupft" ist diese Grundstimmung von "so ein Blödsinn, den die 
da gemacht haben".
Die Jungs (Kernighan und Ritchie) waren schon recht fit.
Dass die sachlichen Argumente auf unvollständigen Informationen (über 
die Bedeutung von Interpretation; über den inneren Zusammenhang von 
Berechnung und Ablaufsteuerung) oder Kategorieirrtümern (Bit und 
Wahrheitswert) beruhen, macht die ausgedrückte Abneigung für mich 
inakzeptabel, jedenfalls irrelevant.

Na, so böse, wie sich das vielleicht anhört, ist es nicht gemeint. 
Nichts für ungut. :-)

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

Axel S. schrieb:
> void puts(char *p)
> {
>   if (p != 0) {
>     while (*p)
>       puts(*p++);
>   }
> }


hallo labersäcke, ich liebe einzeiler!

void puts(char *p) {
   for (char c=*p; c&&p; p++, c=*p) puts(c);
}


mt

von A. S. (Gast)


Lesenswert?

Apollo M. schrieb:
> hallo labersäcke, ich liebe einzeiler!

Und warum dann so langatmig? Das Original war doch:

Axel S. schrieb:
> void puts(char *p)
> {
>   while (p && *p) {putc(*p++);}
> }

Egons Argumente zu bool teile ich übrigens auch nicht.


Bei Side effects gilt vertauschen nie, und da reicht schon ++ oder das 
Lesen von Registern

Und einzelne Bits sind nun mal boolsche Variablen. Bei lPCLint z.b. 
müsste man das explizit abschalten, wenn man das nicht will.

von (prx) A. K. (prx)


Lesenswert?

Egon D. schrieb:
> In dieser Sichtweise sind Statusbits, die von externer
> Hardware gesetzt werden, erstmal nur "Bits".
> Zu "Wahrheitswerten" werden sie erst, wenn sie mittels
> einer passenden Abfrage in ein Flag wandern und den
> Programmablauf beeinflussen.

Was ist in diesem Sinn der Unterschied der AVR Befehle
  SBIS,SBIC - skip if bit in io is set/clear
  SBRS,SBRC - skip if bit in register is set/clear
  BRBS,BRBC - branch if bit in flags register is set/clear
              (BRCS x = BRBS 0,x)
Alle testen exakt ein Bit und ändern abhängig davon den Programmablauf. 
Nur die Orte des Bits unterscheiden sich.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Egon D. schrieb:
> Ich habe den K&R vorliegen. Als historisches Dokument finde ich ihn sehr
> interessant, aber als Leitfaden für das Erlernen von C finde ich ihn
> untauglich.

Kann ich nicht bestätigen. Ich habe C damit hervorragend lernen können 
(mit vorhandenen Pascal-Kenntnissen). War vor knapp 30 Jahren mein 
Berufseinstieg.

Kann sein, dass es bessere gibt, aber es gibt auf jeden Fall schlimmere. 
:)

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

A. S. schrieb:
> Und warum dann so langatmig? Das Original war doch:
>
> Axel S. schrieb:
>> void puts(char *p)
>> {
>>   while (p && *p) {putc(*p++);}
>> }

ja stimmt, das gefällt mir noch besser!

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


Lesenswert?

Apollo M. schrieb:
> A. S. schrieb:
>> Und warum dann so langatmig? Das Original war doch:
>>
>>> void puts(char *p)
>>> {
>>>   while (p && *p) {putc(*p++);}
>>> }
>
> ja stimmt, das gefällt mir noch besser!

Wenn es umbedingt kurz sein muß, kann man sogar noch die geschweiften 
Klammern weglassen. Es gibt aber auch gute Gründe dafür, es nicht als 
Einzeiler und insbesondere auch mit Klammern zu schreiben:

1. als Mehrzeiler mit korrekter Einrückung wird die Struktur der 
Schleife unmittelbar sichtbar.

2. auch wenn eine einzelne Zeile im Schleifenkörper keinen eigenen Block 
mit {} erfordert, schadet es nicht. Und spätestens, wenn mal jemand das 
Programm erweitert und eine weitere Zeile zum Schleifenrumpf hinzufügen 
will, wird der Block gebraucht. Es ist eine häufige Fehlerquelle, in 
dieser Situation einfach eine weitere eingerückte Zeile hinzuschreiben 
und sich dann zu wundern, daß das Programm nicht tut was man will.


Der Compiler erzeugt aus allen diesen Varianten den gleichen Code. Auf 
Quellcodeebene sollte das Ziel immer maximal einfache Lesbarkeit sein.

von lalala (Gast)


Lesenswert?

Jörg W. schrieb:
> Niklas G. schrieb:
>> Du könntestif(!!isActivated() & !!(++i))
>
> Nein, das ändert nichts.
>
> Nochmal, Kurzschluss-Evaluierung ist nur für && und || festgelegt.
>
> Bei & (oder |) ist es dem Compiler frei gestellt, in welcher Reihenfolge
> er die einzelnen Teilausdrücke bewertet, da ändert auch eine doppelte
> Negation nichts dran.

Das ist richtig. Ich möchte hier aber auf einen wichtigen Punkt 
hinweisen, nämlich wenn es i und nicht i++ ist:
if(isActivated() & i)
ist total falsch. Da kann es sein, dass isActivated 1 zurückliefert und 
i 2, dann ist der bitweise verandung 0.
Richtig wäre:
if(!!isActivated() & !!i)
Gut, jetzt kann man sagen, dann nimm doch &&. In dem Fall das da rechts 
nicht i steht sondern eine Funktion mit Seiteneffekten kann dicht nicht 
short-circuit Variante die richtige sein.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

lalala schrieb:
> Gut, jetzt kann man sagen, dann nimm doch &&. In dem Fall das da rechts
> nicht i steht sondern eine Funktion mit Seiteneffekten kann dicht nicht
> short-circuit Variante die richtige sein.

Nein.

Der Fall gehört als schlechter Coding-Style in die Tonne.

von lalala (Gast)


Lesenswert?

Jörg W. schrieb:
> Der Fall gehört als schlechter Coding-Style in die Tonne.

Stimmt. Jedoch gehört dann auch sehr viel anderes was sich sonst so in C 
Code findet in die Tonne.

von Bernd K. (prof7bit)


Lesenswert?

lalala schrieb:
> Jedoch gehört dann auch sehr viel anderes was sich sonst so in C
> Code findet in die Tonne.

Zweifellos (und nicht nur dann sondern immer) gehört vieles was sich 
allerorts (und nicht nur bei C, dort aber auch nicht selten) findet in 
die Tonne.

Und es ist noch keinem ein Zacken aus der Krone gebrochen wenn er die 
vorgesehenen Kontrollstrukturen verwendet hat um den Programmfluss zu 
steuern anstatt das selbe durch geschicktes Ausnutzen subtiler 
Auswerteregeln für boolesche Ausdrücke zu von hinten über die Bande 
gespielt zu erreichen. Und es gibt auch keinen Punktabzug wenn man zwei 
Zeilen braucht um einzulochen statt nur einer halben. Das ist weder 
Minigolf noch Billard.

: Bearbeitet durch User
von Mark B. (markbrandis)


Lesenswert?

Mark schrieb:
> Hi
>
> Angenommen ich möchte if(isActivated() && i++) ausführen.

Ich würde so weit gehen zu sagen, dass so etwas grundsätzlich schlechter 
Programmierstil ist.

Warum?

In das Abfragen der Bedingung(en) gehört logisch gesehen kein Code rein, 
der etwas verändert. Sondern man fragt einfach einen Ist-Zustand ab. 
Also quasi ein Nur-Lese-Zugriff.

Das, was man verändern will - also zum Beispiel die Variable i um eins 
zu erhöhen - gehört in den THEN Teil der bedingten Verzweigung. Also so 
(in Pseudocode):

IF (Zustand ist so und so)
THEN Verändere_dies()
ELSE Verändere_jenes()

In C gibt es kein Schlüsselwort THEN, der entsprechende Block wird in 
geschweifte Klammern {} gefasst.

von Egon D. (Gast)


Lesenswert?

Theor schrieb:

> Auch teile ich Deine Ansicht nicht, dass
> Wahrheitswerte (wenn ich Deine Wortwahl korrekt
> nachvollziehe) "natürlicherweise" den Programmfluss
> steuern, während das Vektoren von Bits ebenso
> natürlicherweise nicht tun.

Nein -- nicht "natürlicherweise", sondern "laut meiner
(an den Haaren herbeigezogenen) Definition".

"Wahrheitswert sein" ist keine intrinsische Eigenschaft
eines Bits, sondern ergibt sich aus der Verwendung im
Programm.


> Diese begriffliche Trennung zwischen Ablauf und
> Berechnung gab es noch vor den Programmiersprachen.
> Realisiert in Maschinen ist sie aber "künstlich".
> Das kann man feststellen, wenn man auf Fälle stösst,
> in denen ein Ablauf durch eine Abfolge von
> Bitvektoroperationen ersetzt wird (oder umgekehrt).

Das stimmt -- und dieser Einwand hat mich letztlich auf
die richtige Fährte geführt. Siehe dazu weiter unten.


> Es ist schon folgerichtig, dass die Operationen "&&"
> usw. ihre Operanden als Zweiwertig betrachten, in dem
> sie 0 als den Repräsentanten von Falsch und alle Werte
> ungleich 0 als die anderen Repräsentanten von Wahr
> betrachten.
> Damit sind das de fakto boolsche Operatoren und nichts
> anderes.

Genau das stimmt nicht -- und Dank Deines Einwandes ist
mir inzwischen auch klar, wieso.

Nehmen wir mal
1
a = b & c;
Hier ist der Sachverhalt völlig klar: Jedes Bit von b
kann unabhängig vom korrespondierenden Bit in c den
Wert "0" oder "1" annehmen. Das gilt umgekehrt auch für
die Bits von c, die unabhängig vom korrespondierenden
Bit in b den Wert "0" oder "1" haben können.
Es ergeben sich die bekannten vier Belegungsmöglichkeiten
für die korrespondierenden Bits von b und c, und daraus
bestimmt sich die Belegung des Ergebnisbits in a.

Soweit nichts Neues.

Selbst für einen Ausdruck wie
1
a = b ^ (b & c);
bleibt die Argumentation richtig, denn obwohl (b & c)
offensichtlich NICHT vom ersten Teilausdruck b unabhängig
ist, kann jedes Bit in jedem Teilterm nur entweder "0"
oder "1" sein. Andere Möglichkeiten gibt es nicht.

Jetzt betrachten wir
1
if ((p != NULL) && (*p != 0)) { ... }

Wo liegt der Unterschied zu den Beispielen oben?

Naja, der auffallendste Unterschied ist, dass die
Teilbedingungen nicht mehr voneinander unabhängig
sind, denn beide hängen "irgendwie" von p ab.

Das kann aber noch nicht der Kern der Sache sein,
denn im Ausdruck "b ^ (b & c)" hängen die Teilterme
"b" und "(b & c)" AUCH BEIDE von b ab, und es gibt
trotzdem kein Problem bei der Auswertung.

Der entscheidende Punkt ist vielmehr, dass der
Teilterm "(*p != 0)" überhaupt nicht DEFINIERT ist,
wenn "(p != NULL)" falsch ist, denn ein ungültiger
Pointer darf nicht dereferenziert werden!
Das bedeutet: "((p != NULL) && (*p != 0))" ist im
strengen Sinn überhaupt kein boolescher Ausdruck, weil
eine Belegung "nicht definiert" kein Bestandteil der
booleschen Algebra ist!

Boolesche Variablen können "wahr" oder "falsch" sein,
und zwar NUR ENTWEDER "wahr" ODER "falsch", und nix
anderes.
Eine boolesche Variable darf auch in einem booleschen
Term fehlen (=nicht enthalten sein), dann ist ihr
Wahrheitswert für die Auswertung dieses Ausdruckes EGAL,
d.h. der Wahrheitswert des Ausdruckes hängt dann nicht
von der Belegung dieser Variablen ab. Dennoch HAT sie
einen der beiden Werte "wahr" oder "falsch".

Ein Term, der eine Variable enthält, die mit dem Wert
"undefiniert" belegt ist, ist schlicht und ergreifend
kein boolescher Ausdruck.


> Und damit sind sie ganz sinnvoll auch Subjekt von
> Kurzschluss-Auswertungen.

Nein -- und zwar insofern nicht, weil es die "Kurz-
schluss-Auswertung" gestattet, Ausdrücken einen
(booleschen) Wahrheitswert zuzuordnen, die gar keine
boolesche Ausdrücke sind!

Das RESULTAT dieser Kurzschluss-Auswertung ist natürlich
nach wie vor ein boolescher Wahrheitswert -- aber die
ARGUMENTE sind keine booleschen Ausdrücke. Die Operatoren
"&&" und "||" werten eine OBERMENGE boolescher Ausdrücke
aus; ich würde sie partiell definierte Ausdrücke nennen.

Hier liegt auch der Zusammenhang zu Deinem Einwand oben,
dass meine (künstliche) Unterscheidung von "Bits" und
"Wahrheitswerten" letztlich willkürlich ist, weil die
sequenzielle Auswertung boolescher Ausdrücke auch nur
eine Folge der verfügbaren Maschinen ist, die halt
sequenziell arbeiten.

Das stimmt genau.
Die "festgelegte Auswertungsreihenfolge" der Kurzschluss-
auswertung kaschiert, dass nicht einfach boolesche
Ausdrücke etwas intelligenter ausgewertet werden, sondern
dass Ausdrücken ein boolescher Wahrheitswert zugeordnet wird,
die überhaupt keine booleschen Ausdrücke sind, sondern einer
Obermenge entstammen!

Das ist der entscheidende Etikettenschwindel.


> Was mich "anzupft" ist diese Grundstimmung von "so ein
> Blödsinn, den die da gemacht haben".

Nein, überhaupt nicht, ganz im Gegenteil.


> Die Jungs (Kernighan und Ritchie) waren schon recht fit.

Natürlich.
C ist für mich ein Gesamtkunstwerk, und je länger ich mich
damit beschäftige, desto interessanter finde ich es, und
desto mehr Dinge finde ich, die mir gefallen.

Das hat aber ÜBERHAUPT nichts mit der Tatsache zu tun, dass
wir heutzutage C nicht mehr so lehren müssen, wie das vor
über 30 Jahren gemacht wurde.


> Na, so böse, wie sich das vielleicht anhört, ist es
> nicht gemeint.
> Nichts für ungut. :-)

Na. Alles gut. Ich danke Dir für Deine Antwort, denn sie
hat mich letztlich auf den rechten Weg geführt.

von Egon D. (Gast)


Lesenswert?

A. K. schrieb:

> Egon D. schrieb:
>> In dieser Sichtweise sind Statusbits, die von externer
>> Hardware gesetzt werden, erstmal nur "Bits".
>> Zu "Wahrheitswerten" werden sie erst, wenn sie mittels
>> einer passenden Abfrage in ein Flag wandern und den
>> Programmablauf beeinflussen.
>
> Was ist in diesem Sinn der Unterschied der AVR Befehle
>   SBIS,SBIC - skip if bit in io is set/clear
>   SBRS,SBRC - skip if bit in register is set/clear
>   BRBS,BRBC - branch if bit in flags register is set/clear
>               (BRCS x = BRBS 0,x)
> Alle testen exakt ein Bit und ändern abhängig davon den
> Programmablauf. Nur die Orte des Bits unterscheiden sich.

Die Unterscheidung von "Bits" und "Wahrheitswerten" ist
nicht durchzuhalten, das ist mir schon klar.

Das war ja auch nur mein verzweifelter Versuch, den von
mir wahrgenommenen grundsätzlichen Unterschied zwischen
"&" und "&&" irgendwie schärfer zu fassen.

Auf die Idee, dass man mittels "&&" partiell definierte
boolesche Ausdrücke auswerten kann, was mit "&" nicht
geht, bin ich halt nicht gleich gekommen. Die
"Auswertungsreihenfolge" ist zusammen mit der "Kurz-
schluss-Auswertung" nur das technische Vehikel dafür.

von Egon D. (Gast)


Lesenswert?

Jörg W. schrieb:

> Egon D. schrieb:
>> Ich habe den K&R vorliegen. Als historisches Dokument
>> finde ich ihn sehr interessant, aber als Leitfaden
>> für das Erlernen von C finde ich ihn untauglich.
>
> Kann ich nicht bestätigen. Ich habe C damit hervorragend
> lernen können (mit vorhandenen Pascal-Kenntnissen).

Das wundert mich nicht wirklich; Du bist offensichtlich
in mehrlei Hinsicht deutlich leidensfähiger als ich :)

von Yalu X. (yalu) (Moderator)


Lesenswert?

Egon D. schrieb:
> Ein Term, der eine Variable enthält, die mit dem Wert
> "undefiniert" belegt ist, ist schlicht und ergreifend
> kein boolescher Ausdruck.

Du hast recht, wenn man die Sache aus der Sicht eines Mathematikers
betrachtet.

Aber:

C und die Mathematik sind nun einmal zwei völlig verschiedene Domänen,
weswegen es völlig legitim ist, dass Begriffe unterschiedlich definiert
sind.

Die Mathematiker haben ihre Definition, was ein logischer Operator
ist, und C-Programmierer haben eben eine leicht davon abweichende. Die
Referenz für die C-Programmierung ist nicht die Mathematik, sondern die
Norm ISO/IEC 9899:2011.

Wären die mathematischen Definitionen auch in C verbindlich, gäbe es in
C noch etliche weitere "falsche" Begriffe, bspw.:

- Variable: In der Mathematik gibt es keine Zuweisungen an Variablen.

- Funktion: In der Mathematik hängt Wert einer Funktion ausschließlich
  von den Argumenten ab.

- Addition: Bei der Addition zweier int-Werte ist in C ein Überlauf,
  d.h. ein undefiniertes Ergebnis möglich, in der Mathematik ist sie
  über den gesamten Wertebereich ihrer Argumente definiert.

- Sinus, Cosinus u.ä.: Diese Funktionen entsprechen nicht ihren
  mathematischen Pendands, da sie i.Allg. nur Näherungswerte liefern.

- u.v.m.

Wenn du über jeden der in C anders definierten Begriffe genauso viel
kritischen Text wie über die logischen Operatoren schreiben möchtest,
bist du wahrscheinlich ein paar Tage beschäftigt. Wenn du damit fertig
bist, kannst du mit C++ und den Begriffen vector, set und functor
weitermachen ;-)

Gerade der Begriff Funktor ist in sofern interessant, dass er nicht
einmal innerhalb der Programmierung einheitlich definiert ist. Er wird
bspw. in den Sprachen C++, Prolog und Haskell in drei völlig
verschiedenen Bedeutungen genutzt. Der Funktor in Haskell kommt dem
mathematischen noch am nächsten, trifft ihn  aber dennoch nicht exakt.
Das ist aber IMHO völlig in Ordnung, solange der Kontext klar ist, in
dem der Begriff verwendet wird.

Haskell ist vom Sprachkonzept und der Begrifflichkeiten "mathematischer"
als die meisten anderen Programmiersprachen, und wenn dort Begriffe aus
der Mathematik verwendet werden, haben sie i.Allg. einen sehr engen
Bezug zu dieser. Dennoch haben auch in Haskell die Operatoren && und ||
Kurzschlussverhalten, weswegen bspw. False && undefined als Ergebnis
False liefert, während bei True && undefined das Ergebnis undefined ist
und bei Verwendung eine ziemlich "unmathematische" Exception auslöst.

Trotzdem ist es durchaus sinnvoll, && und || als logische Operatoren zu
bezeichnen, denn in 95% aller Fälle werden sie in Ausdrücken wie z.B.

  z >= 0 && z < 10

(also mit nebeneffektfreien Operatoren) verwendet, wo tatsächlich
keinerlei Unterschied zu ihrer Verwendung in der Mathematik besteht.

Ebenso wird bei der int-Addition i.Allg. dafür gesorgt, dass kein
Überlauf stattfindet. Unter dieser Randbedingungen verhält sie sich
ebenso wie in der Mathematik.

Warum sollte man sich neue, schwer erlernbare Phantasiebegriffe für
solche Dinge ausdenken, wenn es bereits bekannte gibt, die den Sinn zwar
nicht exakt, aber doch in sehr guter Näherung treffen?

Die Mathematiker machen es ja auch nicht anders und sprechen bspw. von
"befreundeten" Zahlen, obwohl die Freundschaft zwischen Zahlen höchstens
marginal etwas mit dem zu tun hat, was sich ein Nichtmathematiker
darunter vorstellt.

Übrigens werden die bitweisen Operatoren & und | in der C-Norm zurecht
nicht als logische Operatoren bezeichnet, weil ihre Argumente und
Ergebnisse nicht die Wahrheitswerte true und false, sondern ganze Zahlen
sind.

von Bernd K. (prof7bit)


Lesenswert?

Yalu X. schrieb:
> [Haskell] weswegen bspw. False && undefined als Ergebnis
> False liefert,

Kann in Haskell ein boolescher Wert "undefined" sein? Mir ist in 
Erinnerung daß das ein extrem ausgeklügeltes und strenges Typsystem hat, 
ich würde erwarten daß dort ein bool nur genau 2 verschiedene Werte 
haben kann und daß der "undefined"-Fall mit ner Maybe-Monade davon 
komplett wasserdicht isoliert wird? Ist das nicht so?

von W.S. (Gast)


Lesenswert?

Jörg W. schrieb:
> Warum geht es eigentlich nicht in den Kreisen von C-Hassern, sich aus
> Threads bezüglich bestimmter Eigenschaften dieser Programmiersprache
> einfach mal herauszuhalten?

Dein Satz enthält mehrere falsche Annahmen deinerseits.

Erstens hasse weder ich noch viele andere Leute C, wenngleich (ebenso) 
ich und viele andere Leute C nicht wirklich lieben.

Zweitens ist der Blick auf C durchaus weitaus nüchterner und sachlicher, 
wenn man ihn aus etwas Distanz auf diese Sprache wirft. Aber dazu gehört 
eben, solche Distanz haben zu können. Wer sich immer nur C und C-artiges 
beschränkt hat, der hat die Chance auf die benötigte Distanz eben nicht. 
Nein, ich verkneife mir den Term "Geistig Beschränkte" hier an dieser 
Stelle mal nicht. In hunderten ähnlich gelagerter Fälle hab ich das nur 
gedacht, aber nicht gepostet.

Drittens frage ich mich wirklich, warum typische C-Programmierer mit 
Fleiß die jeweils unleserlichste und fehlerträchtigste Art und Weise 
wählen, sich auszudrücken. Als rational denkender Mensch vermute ich 
zuvörderst ja einen tieferen Sinn dahinter. Sollte es diesen jedoch 
nicht geben, dann ist obiger Term noch zu milde geurteilt.


Viertens ist C durchaus auch reformierbar. Soll mir keiner sagen, das 
ginge nicht. Aber: man muß so etwas auch wirklich WOLLEN.

Fünftens sollte man die Diskussion über Mängel (in der sache oder in den 
Köpfen) nicht den jeweiligen Fanboys überlassen, denn das gibt immer nur 
ein schiefes Bild.

So Jörg, jetzt weißt du, warum unsereiner gelegentlich sich auch in 
solchen abstrusen Topics äußert. Es ist im weitesten Sinne ein 
kitzekleiner Beitrag zur Hygiene.

W.S.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

W.S. schrieb:
> Dein Satz enthält mehrere falsche Annahmen deinerseits.

Insbesondere enthielt er Ironie.

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.