Forum: PC-Programmierung bool[8] in uint8_t umwandeln


von N. N. (dewid2)


Lesenswert?

Hallo zusammen,

ich möchte in meinem Code abfragen, ob ein Array nur nullen (und an 
anderer Stelle nur Einsen) enthält. Natürlich wäre es möglich  das byte 
mit 8 Operationen zusammenzusetzen, ich wollte es jedoch effizienter 
machen. Da Arrays in C ja im Speicher immer ununterbrochen angelegt 
werden (?), sollte der folgende Code die Aufgabe einfach und effizient 
übernehmen.

        bool arr[8]= {0,0,0,0,0,0,0,0};
        uint8_t *pointer;
        pointer = &arr[0];

Dies wird mir jedoch durch den Compiler versagt. Dabei sehe ich keinen 
logischen Fehler in meiner Idee. Kann mir jemand sagen wieso das nicht 
geht und wie man es ähnlich effizient machen könnte?

: Bearbeitet durch User
von Jim M. (turboj)


Lesenswert?

Das "bool arr[8]" sind 8 Byte und nicht 8 Bit. Bei einigen Compilern 
sogar mehr, z.B. 32 Bit auf 32-bit Compilern.

Wenn man 8 Bit zu einem Byte zusammenfassen will, bräuchte man 
Bitfields. Die lassen sich aber IIRC nicht als Array ansteuern.

von N. N. (dewid2)


Lesenswert?

Jim M. schrieb:
> Das "bool arr[8]" sind 8 Byte und nicht 8 Bit. Bei einigen Compilern
> sogar mehr, z.B. 32 Bit auf 32-bit Compilern.

Tatsache, das war mir gar nicht klar vorher. Dann werd ich meinen Code 
wohl nochmal umschreiben. Vielen Dank!

von Sven L. (sven_rvbg)


Lesenswert?

Naja könntest Dein Array auch in einer Schleife durchlaufen und mittels 
Schiebeoperationen, die jeweiligen bits im Ausgabebyete setzen oder 
löschen

von foobar (Gast)


Lesenswert?

> Die [Bitfields] lassen sich aber IIRC nicht als Array ansteuern.

Korrekt. Die kleinste adressierbare (notwendig für ein Array) Einheit 
ist ein Byte.

von N. N. (dewid2)


Lesenswert?

Sven L. schrieb:
> Naja könntest Dein Array auch in einer Schleife durchlaufen und mittels
> Schiebeoperationen, die jeweiligen bits im Ausgabebyete setzen oder
> löschen

Dass das so geht war mir auch vorher klar, nur wurde dir Operation 
(mittlerweile geändert) jede Iteration meiner for(;;) Schleife 
durchlaufen, und ich hatte gehofft man könne die 8 Operationen die dein 
Vorschlag benötigt auf eine runterbrechen.



foobar schrieb:
>> Die [Bitfields] lassen sich aber IIRC nicht als Array ansteuern.
>
> Korrekt. Die kleinste adressierbare (notwendig für ein Array) Einheit
> ist ein Byte.

Das scheint universell zu sein oder? Mein 64 bit Desktop kann scheinbar 
auch einzelne bytes adressieren, ebenso wie mein MC.


Btw, wenn ein einzelner bool arr[0]=true (z.B. alle bits gesetzt) und 
uint8_t *pointer die gleiche Größe haben, ist es dann irgendwie möglich 
den pointer auf die adresse von bool zu setzen und somit dessen Wert 
(0xFF) als int auszulesen?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

N. N. schrieb:
> Das scheint universell zu sein oder? Mein 64 bit Desktop kann scheinbar
> auch einzelne bytes adressieren, ebenso wie mein MC.

C(++) definiert das Byte als die kleinste adressierbare Einheit. Ein 
char ist immer genau ein Byte groß. Die Anzahl an Bits pro Byte ist aber 
nicht festgelegt; sie ist mindestens 8 und kann über das Makro CHAR_BIT 
abgerufen werden. Auf den allermeisten Plattformen sind es aber 8. 
uint8_t ist nahezu sicher auf allen Plattformen identisch zu entweder 
unsigned char oder char oder existiert einfach nicht, falls ein char 
mehr als 8 bits hat.

N. N. schrieb:
> Btw, wenn ein einzelner bool arr[0]=true (z.B. alle bits gesetzt)
Es ist nicht festgelegt, welche Bits dann 1 sind. Meistens wird nur das 
unterste Bit genutzt.

N. N. schrieb:
> möglich
> den pointer auf die adresse von bool zu setzen und somit dessen Wert
> (0xFF) als int auszulesen?
Du kannst den Pointer auf char umcasten, was aber 
implementations-spezifisches Verhalten bewirkt und somit unportabel und 
unsauber ist. Auf int umcasten und dereferenzieren ist in C(++) 
verboten. Es kann zwar den Anschein haben zu funktionieren, kann aber 
früher oder später beliebig schief gehen (siehe auch strict aliasing).

Was hast du überhaupt vor? Möchtest du eine Datenstruktur so packen dass 
du sie abspeichern oder verschicken kannst? Dann lies mal 
Serialisierung. Mit der dort auch vorgestellten µSer-Bibliothek kann 
die anfängliche Problemstellung so gelöst werden:
1
#include <cstdint>
2
#include <iostream>
3
#include <uSer/uSer.hh>
4
5
int main () {
6
  bool array [8] = { true, false, true, false, true, false, true, false };
7
  uint8_t packed [1];
8
  uSer::serialize (packed, array);
9
  std::cout << "0x" << std::hex << int { packed[0] } << std::endl;  // Ausgabe 0x55
10
}

Die Ausgabe-Variable "packed" ist ein Array, weil µSer keinen einzelnen 
uint8_t akzeptiert. Das macht letztendlich aber kaum einen Unterschied.

: Bearbeitet durch User
von Jack (Gast)


Lesenswert?

> ob ein Array nur nullen (und an anderer Stelle nur Einsen) enthält

Ganz fies kann das das so testen:
1
bool arr[8] =...;
2
3
if(!memchr(arr, true, sizeof(arr)) {
4
  // nur false (Nullen) in arr[]
5
}
6
7
if(!memchr(arr, false, sizeof(arr)) {
8
  // nur true (Einsen) in arr[]
9
}
memchr() benutzt intern eine Schleife die maximal sizeof(arr) pro 
memchr() durchlaufen wird.

von N. N. (dewid2)


Lesenswert?

Danke für den sehr informativen Beitrag.

Niklas G. schrieb:
> Was hast du überhaupt vor?

Im Moment gar nichts. Ich setze nun die Bits klassisch mit Masken 
einzeln und es funktioniert gut wie erwartet. Die Pointerumwandlung ist 
lediglich aus Interesse. Danke auch hier für deine Tipps, ich werde mal 
überprüfen ob ein bool in meinem System auch i.A. nur über die unterste 
Stelle implementiert ist.

von guest (Gast)


Lesenswert?

Jack schrieb:
> Ganz fies kann das das so testen:bool arr[8] =...;
>
> if(!memchr(arr, true, sizeof(arr)) {
>   // nur false (Nullen) in arr[]
> }
>
> if(!memchr(arr, false, sizeof(arr)) {
>   // nur true (Einsen) in arr[]
> }

Und im zweiten Fall fällt man dann je nach Implementierung ganz fies auf 
die Schnauze.

von Wolfgang (Gast)


Lesenswert?

Niklas G. schrieb:
> Es ist nicht festgelegt, welche Bits dann 1 sind. Meistens wird nur das
> unterste Bit genutzt.

Festgelegt ist true als "ungleich Null", false als "gleich Null".
Alles andere kann jeder Compiler-Bauer machen wie er möchte und hat 
nichts mit C-Standard zu tun.

von Rolf M. (rmagnus)


Lesenswert?

Wolfgang schrieb:
> Niklas G. schrieb:
>> Es ist nicht festgelegt, welche Bits dann 1 sind. Meistens wird nur das
>> unterste Bit genutzt.
>
> Festgelegt ist true als "ungleich Null", false als "gleich Null".

Nein. Wenn man einen Integer ungleich Null in einen bool konvertiert, 
kommt true raus, sonst false. Das sagt aber absolut gar nichts darüber 
aus, was bei dieser Konvertierung passiert und wie die Bits eines bool 
für true und für false aussehen müssen.

von hbl999 (Gast)


Lesenswert?

Guck dir mal diese deklaration an:
typedef struct
{
  unsigned bit0:1;
  unsigned bla:1;
  unsigned murks:1;
  unsigned schund:3;
}tBitfeld;

Da kannst Du jetzt noch eine union drüberlegen und dann entweder als
bit (also bla=1) oder als uint8_t, uint16_t oder uint32_t
ansprechen

von Niklas Gürtler (Gast)


Lesenswert?

hbl999 schrieb:
> Da kannst Du jetzt noch eine union drüberlegen

Das ist im C implementation defined (also unportabel) und im C++ 
komplett verboten. Also nicht die beste Vorgehensweise.

von hbl999 (Gast)


Lesenswert?

was ist dadran unportabel`?

von Jemand (Gast)


Lesenswert?

hbl999 schrieb:
> was ist dadran unportabel`?

C11 6.7.2.1 11:
 The order of allocation of bit-fields within a unit (high-order to
low-order  or  low-order  to  high-order)  is  implementation-defined. 
The  alignment  of  the
addressable storage unit is unspecified

von Niklas Gürtler (Gast)


Lesenswert?

hbl999 schrieb:
> was ist dadran unportabel`?

Da Padding, Byte Order, Integer -Formate sowie die Umsetzung von 
Bitfields plattformspezifisch sind, ist das Ergebnis von solchen 
union-Konstrukten unportabel. Generell sollte man unions ausschließlich 
zum Speicher sparen nutzen, nicht für irgendwelche Konvertierungen. 
Steht auch alles detailliert in Serialisierung.

von hal9000 (Gast)


Lesenswert?

Dummes zeug, das regelt der compilerhersteller. Wenn du das anzweifels 
darfts du gar nix mehr machen.

von Niklas Gürtler (Gast)


Lesenswert?

hal9000 schrieb:
> Dummes zeug, das regelt der compilerhersteller. Wenn du das
> anzweifels darfts du gar nix mehr machen.

Nein, der Compiler warnt sogar teilweise in solchen Fällen, z.B. der GCC 
in Form von Strict Aliasing Verletzungen. Der Compiler kann die 
Unterschiede nicht weg-regeln ohne die Effizienz stark zu 
beeinträchtigen. Im C-Standard würde das wohl kaum so stehen wenn die 
Compiler das dann anders machen!
Und es ist richtig, dass in C wesentlich weniger erlaubt ist als oft 
angenommen. Zur Datenkonvertierung sind aber bitweise Operationen 
erlaubt und portabel.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Hier noch ein Beispiel:
1
#include <stdio.h>
2
#include <stdint.h>
3
#include <inttypes.h>
4
5
union U {
6
  struct {
7
    uint8_t bit0:1;
8
    uint8_t bla:1;
9
    uint8_t murks:1;
10
    uint8_t schund:5;
11
    uint64_t foo : 64;
12
  } tBitfield;
13
  struct {
14
    uint64_t raw [2];
15
  } tRaw;
16
};
17
18
int main () {
19
  union U u;
20
  u.tBitfield.bit0 = 0;
21
  u.tBitfield.bla = 0;
22
  u.tBitfield.murks = 0;
23
  u.tBitfield.schund = 0;
24
  u.tBitfield.foo = 1;
25
  printf ("%" PRIx64 "\n", u.tRaw.raw [1]);
26
}
Wenn man das für AMD64 kompiliert, kommt heraus:
1
$ gcc -m64 test.c && ./a.out 
2
1
Kompiliert man hingegen für IA-32:
1
$ gcc -m32 test.c && ./a.out 
2
fff36bfc00000000
Ist also plattformabhängig, d.h. unportabel. In C++ ist es wie gesagt 
ganz verboten. Der Unterschied stammt hier konkret vom Padding, denn bei 
AMD64 hat der 64bit-Integer ein 8-Byte-Alignment, es werden 7 leere 
Bytes davor eingefügt. Auf IA-32 werden nur 3 benötigt. Solche 
Unterschiede kann man teilweise mit Compiler-Optionen beeinflussen - das 
macht es aber nur noch schlimmer, wenn das Verhalten des Programms sich 
schon von Compiler-Optionen stören lässt!

Korrekt und portabel geht das nur mit Bit-Operationen. Diese werden von 
meiner µSer-Bibliothek komfortabel gekapselt, damit könnte das dann so 
aussehen:
1
#include <iostream>
2
#include <cstdint>
3
#include <uSer/uSer.hh>
4
5
struct tBitfield {
6
  USER_STRUCT(tBitfield, )
7
  std::uint8_t bit0;
8
  USER_MEM_ANNOT(bit0, uSer::Width<1>)
9
  std::uint8_t bla;
10
  USER_MEM_ANNOT(bla, uSer::Width<1>)
11
  std::uint8_t murks;
12
  USER_MEM_ANNOT(murks, uSer::Width<1>)
13
  std::uint8_t schund;
14
  USER_MEM_ANNOT(schund, uSer::Width<5>, uSer::Padding::Fixed<24+32>)
15
  std::uint64_t foo;
16
  USER_MEM_ANNOT(foo, uSer::Width<64>)
17
18
  USER_ENUM_MEM(bit0, bla, murks, schund, foo)
19
};
20
21
int main () {
22
  tBitfield bf;
23
24
  bf.bit0 = 0;
25
  bf.bla = 0;
26
  bf.murks = 0;
27
  bf.schund = 0;
28
  bf.foo = 1;
29
  std::uint64_t raw [2];
30
  uSer::serialize (raw, bf);
31
  std::cout << std::hex << raw [1] << std::endl;
32
}
Das Padding wird so gewählt dass es der AMD64-Version entspricht; somit 
ist, unabhängig von der tatsächlichen Plattform, Compiler-Optionen und 
-Version, die Ausgabe immer gleich:
1
$ g++ -m64 -std=c++17 test.cc && ./a.out 
2
1
1
$ g++ -m32 -std=c++17 test.cc && ./a.out 
2
1

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.