Hallo,
ich habe mir grade die Beschreibung zu Bitfeldern im Tutorial angesehen.
Ich verstehe nur nicht warum man da kein unsigned char oder uint8_t
nimmt.
Also was ist besser:
1.:
1
struct{
2
unsignedbStatus_1:1;// 1 Bit für bStatus_1
3
unsignedbStatus_2:1;// 1 Bit für bStatus_2
4
unsignedbNochNBit:1;// Und hier noch mal ein Bit
5
unsignedb2Bits:2;// Dieses Feld ist 2 Bits breit
6
// All das hat in einer einzigen Byte-Variable Platz.
7
// die 3 verbleibenden Bits bleiben ungenutzt
8
}x;
2.:
1
struct{
2
unsignedcharbStatus_1:1;// 1 Bit für bStatus_1
3
unsignedcharbStatus_2:1;// 1 Bit für bStatus_2
4
unsignedcharbNochNBit:1;// Und hier noch mal ein Bit
5
unsignedcharb2Bits:2;// Dieses Feld ist 2 Bits breit
6
// All das hat in einer einzigen Byte-Variable Platz.
7
// die 3 verbleibenden Bits bleiben ungenutzt
8
}x;
Oder 3.:
1
struct{
2
uint8_tbStatus_1:1;// 1 Bit für bStatus_1
3
uint8_tbStatus_2:1;// 1 Bit für bStatus_2
4
uint8_tbNochNBit:1;// Und hier noch mal ein Bit
5
uint8_tb2Bits:2;// Dieses Feld ist 2 Bits breit
6
// All das hat in einer einzigen Byte-Variable Platz.
Weil es völlig wurscht ist.
Laut C-Standard muss an dieser Stelle entweder ein signed int oder ein
unsigned int stehen. Wobei das int im Grunde Nebensache ist. Es geht nur
um die Spezifikation ob ein Bithaufen ein Vorzeichen haben soll oder
nicht. Aber abgesehen davon ordnet sich der Compiler die Bits selber an
und benutzt soviele Bytes wie er dazu braucht.
Joachim Drechsel schrieb:> ... und einem einzelnen Bit ist das Vorzeichen schnuppe. Wo soll es auc> hin ?
Erm? Seit wann braucht im 2er Komplement das Signum ein zusätzliches
Bit?
0b0 -> 0
0b1 -> -1
Schnuppe ist es nicht. Multipliziere mal mit einem größerem Ordinal.
Was ist denn das für eine Argumentation? Wenn wenn ich ein unsigned
forcieren würde, dann natürlich würde dort unsigned ausgegeben werden.
Aber: a und b sind signed. Sie werden in der impliziten Typwandlung bei
der Parameterübergabe auf die nötoige Bitbreite erweitert und das
Vorzeichen mitgenommen. Würde das nicht geschehen, würde 0b00...00 und
0b00...01 übergeben werden, was auch als signed interpretiert 0 und +1
ist.
Das jedoch widerspricht der Ausgabe.
Du kannst gerne auch kreuz und quer damit rechnen. Addier es auch ein
unsigned char oder was immer du willst. ohne expliziten Cast ist die
Interpretation signed und somit wie von mir dargestellt.
@Joachim
Das mag schon sein, dass man das für unsinnig hält. Wie oft braucht man
denn wirklich eine Variable die entweder 0 oder -1 sein kann?
Es ist jedoch einfach nur konsequent durchgezogen und ergibt sich ganz
von alleine in der Form. Und in einem gewissen Sinne bin ich froh, dass
es da keine Ausnahmeregelung gibt. Wenn man einer Abfolge von n Bits ein
Vorzeichen verpassen kann, dann muss das auch für n gleich 1 gelten
können. Und das tuts ja auch. In völliger Analogie (und mit allen
Konsequenzen wie zb mgl. Zahlenbereich) zum Fall n>1.
Ausnahmen in einer Programmiersprache sind immer schlecht.
Joachim Drechsel schrieb:> Sorry, aber ich halte es für so richtig unsinnig, einem einzelnen> Bit ein Vorzeichen unterzuschieben.
Was soll daran unsinniger sein als zwei, drei oder acht Bits ein
Vorzeichen unterzuschieben?
Anstatt der beiden Zustände 0 und 1 hat man dann eben die Zustände 0 und
-1. Ist 1 sinnvoller als -1?
Die signed/unsigned-Deklaration ändert nichts an der Breite des
Datentyps (fügt also im signed-Fall kein Vorzeichen-Bit hinzu) sondern
beeinflusst lediglich die Interpretation des Bitmusters (und damit
verbunden implizite Typumwandlungen durch den Compiler).
Christian
Konsequent bedeutet nicht immer sinnvoll.
Speziell in diesem Fall vermutlich eine echte Falle. Persönlich halte
ich mich mit signed oder unsigned nicht größer auf. Für mich ist ein
bit ein Bit, ein Byte ein Byte und gut isses ;)
Die Idee einer 1-Bit-Variable noch ein Vorzeichen zu verpassen halte
ich für gewagt (zumindest müßte dann ja ein Vorzeichenbit und eins
für die Stelle an sich vorhanden sein. Nur - wo ist es ?).
Danke erstmal für die ganzen Erklärungen. Ich fand es nur seltsam, dass
in der Beschreibung dauernd von unsigned char die Rede ist und dann im
Beispiel wird nur unsigned verwendet. Und wiederum hab ich nun schön
öfters gelsen, dass man lieber uint8_t verwenden sollte. Da kommt man
echt durcheinander und weiß nicht was man nehmen soll (jedenfalls als
Anfänger).
Mathias
Joachim Drechsel schrieb:> (zumindest müßte dann ja ein Vorzeichenbit und eins> für die Stelle an sich vorhanden sein. Nur - wo ist es ?)
Die fehlen die Grundlagen.
Schaue mal in Google, Wikipedia, einem Fachbuch oder dem Ort deiner Wahl
nach den verschiedenen Vorzeichenbehafteten Datenformaten. dort wirst du
neben Vorzeichenbit auch auf Konstrukte wie 1er und 2er Komplement
stoßen mit ihren vor und Nachteilen.
Joachim Drechsel schrieb:> zumindest müßte dann ja ein Vorzeichenbit und eins> für die Stelle an sich vorhanden sein. Nur - wo ist es ?).
Das "Vorzeichenbit" und "die Stelle" sind ein und dasselbe Bit. Die
beiden Bit-Darstellungen "signed 1" und "unsigned 1" werden nur jeweils
unterschiedlich interpretiert. Genauso wie bei einem Byte ein "signed
0b10000000" und ein "unsigned 0b10000000" unterschiedlich interpretiert
werden. Ersteres ist -128, letzteres 128. In beiden Fällen sieht die
Bit-Darstellung identisch aus. Jetzt kann man nach und nach von rechts
beginnend Bits abschneiden
signed 0b1000000 => -64
unsigned 0b1000000 => 64
...
signed 0b10 => -2
unsigned 0b10 => 2
bis man bei einem Bit landet.
Christian
Joachim Drechsel schrieb:> Leute - es ging um Bitfelder nicht um Spitzfindigkeiten in der> Darstellung binärer Zahlen.
Na,ja.
Du hast die Diskussion darüber eröffnet.
Joachim Drechsel schrieb:> C ist mir durchaus geläufig.
Mir schon auch. Und trotzdem manchmal überraschend... ;-)
Aber zum Glück haben die Zahlendarstellung und das Zweierkomplement nur
ganz am Rande und fast gar nichts mit C zu tun...
> Konsequent bedeutet nicht immer sinnvoll.
Und was soll das jetzt heißen?
Soll man die C-Definition so ändern:
In Bitfeldern kann man eine Anzahl von Bits zu einer Einheit
zusammenfassen.
Diese 'Einheit' kann oder kann auch nicht ein Vorzeichen haben, es sei
denn es handelt sich um ein einzelnes Bit, welches immer nur 'kein
Vorzeichen' haben kann.
Wozu die Ausnahme? Braucht kein Mensch.
Wenn es einen Unterschied zwischen BitFeld1 und BitFeld2 in ...
1
structTest
2
{
3
unsignedBitFeld1:3;
4
signedBitFeld2:3;
5
};
... gibt, dann ist es nur konsequent, wenn es genau den gleichen
Unterschied auch bei ...
1
structTest
2
{
3
unsignedBitFeld1:1;
4
signedBitFeld2:1;
5
};
... gibt. Aus welchem Grund soll die Bitzahl da einen Unterschied
machen, ob BitFeld2 einmal korrekt und einmal ein Syntax Error sein
soll? Weil dir das unsinnig vorkommt? Sorry, das ist mir als Begründung
zu wenig, damit man es in eine ISO Norm mit aufnehmen sollte.
Joachim Drechsel schrieb:> es ging um Bitfelder nicht um Spitzfindigkeiten in der> Darstellung binärer Zahlen
OK. Jetzt hat es doch wieder mit C zu tun :)
1
#include<stdio.h>
2
3
struct
4
{
5
signedflag:1;
6
}flags;
7
8
intmain(void)
9
{
10
flags.flag=1;
11
printf("%d\n",42*flags.flag);
12
return0;
13
}
Frage: Welche Zahl erscheint auf der Konsole?
Christian
Christian Gudrian schrieb:> OK. Jetzt hat es doch wieder mit C zu tun :)
:-)
> Frage: Welche Zahl erscheint auf der Konsole?
Letzen Endes handelt es sich bei deinem Beispiel um die "Analogie in 1
Bit" von
1
intmain(void)
2
{
3
signedcharc;
4
5
c=128;
6
printf("%d\n",c);
7
return0;
8
}
Du weist einer Variable einen Wert ausserhalb ihres Zahlenbereichs zu.
Was soll da passieren?
Karl Heinz Buchegger schrieb:> Du weist einer Variable einen Wert ausserhalb ihres Zahlenbereichs zu.
Das -1 (Bit) wird für die Berechnung aufgebohrt zu einem "richtigen" -1
(Integer), dann mit dem 42 (Integer) mutlipliziert und ausgegeben...
Karl Heinz Buchegger schrieb:> Du weist einer Variable einen Wert ausserhalb ihres Zahlenbereichs zu.
Und genau das wollte ich verdeutlichen: den Zahlenbereich eines
vorzeichenbehafteten Bits.
Christian
Die Physiker haben es ja auch geschafft, ein Proton in drie Quarks
zu zerlegen ... Auf ein Bit übertrgen könnte man noch gleich 2
Vorzeichen in einem Bit unterbringe :-)))
Ok, dann haben wir jetzt also ein Bitfield aus Vorzeichen.
Joachim Drechsel schrieb:> Ok, dann haben wir jetzt also ein Bitfield aus Vorzeichen.
Genau. Vorzeichenbehaftete Bits können die Werte + und - annehmen,
vorzeichenlose 0 und 1.
Christian
Christian Gudrian schrieb:> Genau. Vorzeichenbehaftete Bits können die Werte + und - annehmen,> vorzeichenlose 0 und 1.
Was wiederum eine reine Frage der Interpretation ist.
Lothar Miller schrieb:> Karl Heinz Buchegger schrieb:>> Du weist einer Variable einen Wert ausserhalb ihres Zahlenbereichs zu.> Das -1 (Bit) wird für die Berechnung aufgebohrt zu einem "richtigen" -1> (Integer), dann mit dem 42 (Integer) mutlipliziert und ausgegeben...
Schon klar.
Das ist das, was bei Verwendung eines 2-er Komplements passiert.
Aber aus Sicht des C-Standards, steht explizit im Standard, dass das
Ergebnis unspezifiziert ist. Overflows (und im Grunde handelt es sich
bei
flags.flag = 1;
um einen Overflow) sind nur für unsigned Datentypen spezifiziert. Sobald
signed im Spiel ist, hält sich der Standard aus jeglicher Interpretation
raus.
Im 2-er Komplement ist der Wertebereich einer Zahl mit n Bit
-(2^(n-1)) ... (2^(n-1)-1) // ^ bedeutet 'hoch'
Bitzahl -(2^(n-1)) (2^(n-1)-1) Menge d. darstellb. Zahlen
8 -128 127 256
7 -64 63 128
6 -32 31 64
5 -16 15 32
4 -8 7 16
3 -4 3 8
2 -2 1 4
1 -1 0 2
und eine Zuweisung
Variable = 2^(n-1)
ist ein Overflow, weil der positive Wertebereich nun mal nur bis
(2^(n-1)-1) geht und 2^(n-1) im 2-er Komplement damit nicht
darstellbar ist.
Christian Gudrian schrieb:> Vorzeichenbehaftete Bits können die Werte + und - annehmen,
Was ist dann das Ergebnis von dem hier: 42 * -
Und was kommt heraus bei: 42 * +
Sieh dir das mal an: http://codepad.org/DXUGEwcT
Und dir wird klar:
- ein negatives vorzeichenbehaftetes Bit hat den Wert -1.
- ein "positives" vorzeichenbehaftetes Bit hat den Wert 0.
Es kann also die Werte 0 und -1 annehmen.
Christian Gudrian schrieb:> Lothar Miller schrieb:>> Es kann also die Werte 0 und -1 annehmen.> Das sagte ich bereits mehrfach.
Partielle Amnesie?
Christian Gudrian schrieb:> Vorzeichenbehaftete Bits können die Werte + und - annehmen
Die Null (in Zahlen 0) hat kein Vorzeichen...
Karl Heinz Buchegger schrieb:> Overflows (und im Grunde handelt es sich> bei> flags.flag = 1;> um einen Overflow)
Richtig, das fehlende Vorzeichen hatte ich übersehen... :-/
Christian Gudrian schrieb:> Ich sehe, wir verstehen uns. :)
Das ist mir schon klar :)
Ich verwende Bitfelder sehr selten (ich programmiere fast nur für
CGI/Windows), damit Speicher sparen bzw Ports adressieren ist eher
bei den kleine schwarzen Käfern angesagt. Da ist das recht praktisch
obwohl ich mir über die Performance da nicht ganz im Klaren bin.
Da muß ich mir mal genauer den Asembler-Output ansehen.
Jetzt erst mal eine Flasche Schampus köpfen - der Sack ist weg.
Joachim Drechsel schrieb:> Ok, dann haben wir jetzt also ein Bitfield aus Vorzeichen.
Das ist leider ebenfalls nicht richtig. Es gibt hier kein Vorzeichenbit
im Klassischen.
Du hast 0 und -1. Hättest du das Vorzeichen müsste es +1 und -1 sein.
Nur dann könnte man das Vorzeichen auf einen anderen Wert durch
Multiplikation übertragen.
Verabschiede dich von der Vorstellung des Vorzeichens als separate
Information.
Gehe bei der Interpretation so vor:
Wenn das höchstwertige Bit 1 ist, so ist der absolute Wert einer signed
ordinalen Variablen durch die Addition aller Bits multipliziert mit
ihrem jeweiligen Wert gegeben. Ihr Vorzeichen ist Positiv.
Wert = + summe (from i=0 to bitbreite-1) über (bit(i) * 2 hoch i)
Wenn das höchstwertige Bit 1 ist, so ist der absoluter Wert einer signed
ordinalen Variablen durch die addition aller invertierten Bits
multipliziert mit ihrem jeweiligen Wert plus 1 gegeben. Ihr Vorzeichen
ist Negativ.
Wert = - summe (from i=0 to bitbreite-1) über ((1-bit(i)) * 2 hoch i)
Du siehst in beiden Fällen wird zwar die Interpretation über das
höchstwertige Bit gesteuert, jedoch ihr Wert über alle Bits (inklusive
des höchstwertigen) gebildet.
Daher steckt in dem signed:1 := -1 tatsächlich die Information über Wert
und Vorzeichen gemeinsam in dem einen Bit.
Joachim Drechsel schrieb:> Da ist das recht praktisch> obwohl ich mir über die Performance da nicht ganz im Klaren bin.
Es lohnt sich nicht.
Ich nehme auch für jeden Eingang und für jedes Flag gleich ein ganzes
Byte. Dadurch wird der Code schneller (Abfragen sind einfacher) und
besser wartbar. Ich schreibe einfach ein neues Flag hin, und schere mich
nicht darum, ob da noch irgendwo in einem Bitfeld Platz ist, oder wo ich
das reinbasteln kann. Und gebe nicht die Kontrolle an den Compiler ab,
der die Dinger legen darf, wie er lustig ist. Das hässlichste an den
Bitfeldern ist aber, dass man keinen Pointer auf so ein Flag übergeben
kann...
max schrieb:> Es gibt hier kein Vorzeichenbit im Klassischen.
Es gibt kein "klassisches" Vorzeichenbit!
Man kann lediglich am MSB sehen, ob es eine negative Zahl ist.
Das Bit ist aber immer signifikant für den Wert der Zahl.
Lothar Miller schrieb:> Es lohnt sich nicht.
Ich bin auch wieder davon abgekommen. Es ist zu umständlich. Wenn
die dann noch nicht mal 0 werden können ... Ohne Pointer ist das
sowieso nichts (ich nehme an, Du meinst Pointer auf die einzelnen
Bits).
Joachim Drechsel schrieb:> ich nehme an, Du meinst Pointer auf die einzelnen Bits
Richtig.
Du kannst keine generische Funktion schreiben, immer nuß der
Rückgabewert an das Bit zugewiesen werden...
Lothar Miller schrieb:> max schrieb:>> Es gibt hier kein Vorzeichenbit im Klassischen.> Es gibt kein "klassisches" Vorzeichenbit!> Man kann lediglich am MSB sehen, ob es eine negative Zahl ist.> Das Bit ist aber immer signifikant für den Wert der Zahl.
Das ist das was ich schrieb.
"Im Klassisch" im Sinne von naiv, Gedankenbezogen, so wie es dies für
andere Speicherarten, wie z.b. Ieee 754 existiert.
Lothar Miller schrieb:> Es lohnt sich nicht.> Ich nehme auch für jeden Eingang und für jedes Flag gleich ein ganzes> Byte. Dadurch wird der Code schneller (Abfragen sind einfacher) und> besser wartbar.
Das ist einer der Dinge die ich an Java mag. Saubere Typtrennung die
dich dann auch zwingt sauber zu programmieren. Boolean hat true und
false und man kann ihm auch keine 1 oder 0 zuweisen.
Solange das kein kleiner µC ist ist es auch sch...egal ob für ein
boolean 1 byte verbraten wird.
Wobei ich natürlich nicht mit java µCs programmieren möchte, ganz klar!
Saubere Typtrennung etcpp kann man machen wenn die Ressourcen auf
dem Zielsystem vorhanden ist, speziell Java ist da doch recht "üppig".
Auf einem ATTiny2313 (habe ich im Moment in der Mangel) würde das
wohl am Thema vorbei- und überhaupt nicht gehen.
Klar können mir in C auf diese Art Fehler passieren, ich habe aber
dann auch die Chance den zu beheben. Macht mir Java Mist, stehe ich
dumm da (gebranntes Kind, mir ist das mal bei dBASE IV passiert).
Um auf die eingangs gestellte Frage zurückzukommen:
1 & 2 sind hier behandelt:
http://codepad.org/tU1MGQKt
3 ist irrelevant, weil mit 2 identisch (was ist uint8_t anderes als
unsigned char, jedenfalls auf den üblichen Prozessorarchitekturen, die
wir hier zu 99.95% verwenden?=
Rufus Τ. Firefly schrieb:> 1 & 2 sind hier behandelt:> http://codepad.org/tU1MGQKt
Und 4 kommt raus, weil der Compiler dahinter offenbar ein 32 Bit
Compiler ist (unsigned int = 32 Bit). Beim AVR-Studio kommt da beides
mal 1 raus...
Christian Gudrian schrieb:> Warum nicht?
Weil ich mir da nicht Byte Code, ein Interpreter bzw. JIT-Compiler,
dynamische Speicherverwaltung, garbage collector etc. pp. antun möchte.
Zumindest nicht auf 8 oder 16 Bittern.
Mein Einwurf mit java sollte eigentlich nur verdeutlichen daß C in
Sachen Datentypen halt viel Schweinerei zulässt und daß es da
'modernere' (nicht effizientere) Lösungen gibt für die ich froh bin.
Wir sollten hier aber kein Seitenthema Java versus C aufmachen.
Joachim Drechsel schrieb:> Effizient und funktioniert, was soll daran schlecht sein ?
Man kann eine Mutter auch mit einer Säge lösen.
Nicht alles was funktioniert ist auch gut.
Diejenigen die beide Sprachen beherrschen haben verstanden was ich sagen
will, auch wenn sie vieleicht anderer Meinung sind.
Wenn du jier ein Java versus C Bashing aufziehen willst dann ohne mich.
Mathias schrieb:> Hmm immer noch nicht, kannst du mal deine main.c bitte posten.
Zeig doch mal deine.
Schliesslich hängt es von deinem Programm ab und wo genau du im Programm
stehst, wenn du dir im Debugger eine Variable anzeigen lassen willst. Du
Variable muss existieren. Eine funktionslokale Variable existiert aber
ausserhalb ihrer Funktion nicht.
int main(void)
{
int sizofx = sizeof(x);
int sizofy = sizeof(y);
Compiler optimieren.
Eine Variable, die nicht lesend benutzt wird, braucht auch keiner.
Daher: sie fliegt raus.
Das spart SRAM und Rechenzeit.
-> Optimizer ausschalten oder irgendetwas einbauen, so dass die Variable
benutzt wird und nicht wegzuoptimieren ist.
Mathias schrieb:> Jo Danke, habs jetzt so gemacht.
Und?
Was soll das ändern?
Deine Variablen sizeofx bzw. y werden immer noch nirgends verwendet und
sind damit Kandidaten zum wegoptimieren.
Schau:
Überleg dir einfach folgendes: Ändert sich das beobachtbare Verhalten
(und damit ist nicht der Debugger gemeint), wenn man die Anweisungen
einfach weglässt?
Bei dir: ja.
Es ist völlig egal, ob der sizeof an die Variable zugewiesen wird oder
nicht. Deswegen ändert sich am Programmverhalten genau gar nichts. Ergo
wird der Compiler das einfach weglassen wenn er optimieren darf.
Aber ich hab doch Optimization ausgestellt. Danach waren immer noch
keine Werte im Watch-Fenster. Nachdem ich dann das ganze in die Schleife
reingemacht habe, hatte ich dann Werte. Einen Breakpoint nach den beiden
zeilen konnte ich auch nicht setzen, denn da gabs immer ein
Meldungsfenster.
Unzwar stand da folgendes:
"One or more breakpoints or tracepoints could not be set and have been
disabled. The program has been stopped at the reset vector. Do you want
to continue execution?"
Mathias schrieb:> Aber ich hab doch Optimization ausgestellt.
Das kann ich aber nicht wissen.
> "One or more breakpoints or tracepoints could not be set and have been> disabled. The program has been stopped at the reset vector. Do you want> to continue execution?"
Du hattest einen Breakpoint auf einer Zeile, die es so nach einer
Programmänderung nicht mehr gab. Daher hat dich der Debugger darüber
informiert, dass er den Breakpoint auflösen musste.
Karl Heinz Buchegger schrieb:> Noch ein Hinweis:> globale Variablen kann der Compiler nicht wegoptimieren.
Aber die Zuweisung. Nur global machen wird nichts ändern.
Global und volatile sollte aber helfen.
EDIT:
Ok, hiermit
> Aber ich hab doch Optimization ausgestellt.
hat sich dieser Zweig der Diskussion eh erledigt.