Hilfe um 8 Bitfelder in uint8_t umzuwandeln (ESP32)

OP #7490229
Lesenswert?

Hallo,

ich habe folgende Struct:

struct { uint8_t bit7:1; uint8_t bit6:1; uint8_t bit5:1; uint8_t bit4:1; uint8_t bit3:1; uint8_t bit2:1; uint8_t bit1:1; uint8_t bit0:1; } Input;

Hierüber kann ich ja auf jedes einzelne Bit mit Beispiel: if (Input.bit0==1) zugreifen, soweit klar. Jetzt möchte ich aber z.B. alle 8 Bits gleichzeitig aus einer uint8_t lesen, wie mache ich das? uint_8 Data = 0b10101010; Input = Data; funktioniert so natürlich nicht.

Wie muss ich das machen?

#7490239
Lesenswert?

In C gibt es union:

1
typedef union{
2
struct  {
3
  uint8_t bit7:1;
4
  uint8_t bit6:1;
5
  uint8_t bit5:1;
6
  uint8_t bit4:1;
7
  uint8_t bit3:1;
8
  uint8_t bit2:1;
9
  uint8_t bit1:1;
10
  uint8_t bit0:1;
11
} Input;
12

13
uint8_t Byte;
14
} Input_ut;

Aber Vorsicht, Bitfields sind weder portabel noch atomar - man es vermeiden diese direkt für Register Zugriffe zu verwenden.

#7490305
Lesenswert?

Markus S. schrieb:

Mit deinem Code funktioniert Input.bit0 usw. nicht mehr...

Das geht schon. Nur kenne ich die Reihenfolge der Bits andersrum.

1
#include<stdio.h>
2

3
typedef union{
4
struct  {
5
  uint8_t bit0:1;
6
  uint8_t bit1:1;
7
  uint8_t bit2:1;
8
  uint8_t bit3:1;
9
  uint8_t bit4:1;
10
  uint8_t bit5:1;
11
  uint8_t bit6:1;
12
  uint8_t bit7:1;
13
} Input;
14
uint8_t Byte;
15
} Input_ut;
16

17
int main()
18
{
19
    Input_ut Test;
20
    Test.Byte=0;
21
    //Bit 0 setzen und ausgeben
22
    Test.Input.bit0 =1;
23
    printf("%d\n", Test.Byte);
24
    //Ausgabe: 1
25

26
    //Bit 7 setzen und ausgeben
27
    Test.Input.bit6 =1;
28
    printf("%d\n", Test.Byte);
29
    //Ausgabe: 65 (64+1)
30

31
    return 0;
32
}
#7490311
Lesenswert?

N. M. schrieb:

Das geht schon.

Nein, da muss noch ein zusätzliches .Input verwendet werden.

Ganz am Anfang:

1
struct { ... } Input;
2

3
Input.bit0 = 1;

Deine Union:

1
Input_ut Input;
2

3
Input.Input.bit0 = 1;

Wenn man die Struktur "Input" in Deiner Union /namenlos/ lässt, geht's aber:

1
typedef union
2
{
3
  struct  
4
  {
5
    uint8_t bit0:1;
6
    uint8_t bit1:1;
7
    uint8_t bit2:1;
8
    uint8_t bit3:1;
9
    uint8_t bit4:1;
10
    uint8_t bit5:1;
11
    uint8_t bit6:1;
12
    uint8_t bit7:1;
13
  };
14
  uint8_t Byte;
15
} Input_ut;

(abgesehen davon hast Du den Threadstarter vermutlich schon damit verwirrt, daß Du die union als typedef angelegt und nicht direkt instanziiert hast)

#7490316
Lesenswert?

Harald K. schrieb:

Wenn man die Struktur "Input" in Deiner Union /namenlos/ lässt, geht's aber:

Genau so steht es ja auch in dem Beispiel das ich gemacht hatte. Ich hatte nur das Beispiel von turboj weiter verwendet.

Harald K. schrieb:

Nein, da muss noch ein zusätzliches .Input verwendet werden.

Sollte der TO damit überfordert sein, dann sollte er dringend vorher (noch)mal ein C/CPP Buch lesen.

Harald K. schrieb:

Wenn man die Struktur "Input" in Deiner Union /namenlos/ lässt, geht's aber

Und wenn er der struct noch einen Konstruktor verpasst kann er die Struktur auch gleich noch initialisieren. Er verwendet ja sowieso einen CPP Compiler.

#7490370
Lesenswert?

N. M. schrieb:

Aber das Beispiel macht richtige Zugriffe, übersetzt und macht richtige Ausgaben.

Ja, aber es /muss anders geschrieben werden/ als das Original.

Bleibt die Struktur namenlos, ist das nicht der Fall.

Dann nämlich kann man schreiben

1
Input_ut In;
2

3
In.bit0 = 1;
4

5
In.Byte = 0xff;

Hat die Struktur einen Namen, muss man das hingegen so schreiben:

1
Input_ut In;
2

3
In.Input.bit0 = 1; // <-- Da!
4

5
In.Byte = 0xff;
#7490426
Lesenswert?

N. M. schrieb:

Nur kenne ich die Reihenfolge der Bits andersrum.

Das darf der Compilerprogrammierer machen, wie er Bock hat oder wie er es für einfacher hält. Das ist gemeint, wenn hier jemand von undefiniertem Verhalten schreibt (undefined behavior = UB). Man kann sich noch nichtmal beschweren, wenn er seine Meinung von Compilerversion zu Compilerversion ändert. Wer den Compiler oder gar noch den Zielprozessor wechselt hat noch größere Chancen, dass Unerwartetes geschieht. Wer will, darf die Mitglieder der Struktur auch in der .h(pp)-Datei anders anordnen als in der .c(pp)-Datei, oder von 8 auf 40 Mitglieder aufstocken. Irgendwer gräbt vielleicht noch einen Compiler aus, der die Namen der Mitglieder nach dem Alphabet anordnet oder in der Reihenfolge des ersten Lesezugriffs, selbstverständlich abhängig von der Reihenfolge, in der die Dateien übersetzt werden.

Langer Rede kurzer Sinn : verlasse Dich niemals darauf, wie ein Compiler etwas hinter den Kulissen umsetzt.

1
Input.bit0 = (Data >> 0U) & 1U;
2
Input.bit1 = (Data >> 1U) & 1U;
3
Input.bit2 = (Data >> 2U) & 1U;
4
Input.bit3 = (Data >> 3U) & 1U;
5
Input.bit4 = (Data >> 4U) & 1U;
6
Input.bit5 = (Data >> 5U) & 1U;
7
Input.bit6 = (Data >> 6U) & 1U;
8
Input.bit7 = (Data >> 7U) & 1U;

Und zurück :

1
Data = ((uint8_t)(Input.bit0) << 0U) |
2
  ((uint8_t)(Input.bit1) << 1U) |
3
 ((uint8_t)(Input.bit2) << 2U) |
4
 ((uint8_t)(Input.bit3) << 3U) |
5
 ((uint8_t)(Input.bit4) << 4U) |
6
 ((uint8_t)(Input.bit5) << 5U) |
7
 ((uint8_t)(Input.bit6) << 6U) |
8
 ((uint8_t)(Input.bit7) << 7U);

Das mit dem "Funktionstemplate bit<>()" ist noch besser, aber halt auch höhere C++ Kunst und sollte, wie Wilhelm M. (wimalopaan) schrieb, intern so wie die Beispiele hier funktionieren.

#7490521
Lesenswert?

Frank O. schrieb:

Das darf der Compilerprogrammierer machen, wie er Bock hat oder wie er es für einfacher hält. Das ist gemeint, wenn hier jemand von undefiniertem Verhalten schreibt (undefined behavior = UB).

Das ist "nur" implementation-defined ;-) Machts aber auch nicht besser.

Was in C++ UB ist, ist der Zugriff auf ein non-active-member der union. In C ist diese Art von type-punning explizit erlaubt. In C++ geht das wegen ggf. DT mit nicht-trivialen Konstruktoren / Destruktoren nicht (ohne weiteres, möglich ist es schon ...).

#7494687
Lesenswert?

Ich hab sehr viele structs die ich füllen muss, deswegen habe ich memcpy() genutzt. funktioniert mit __attribute__((_packed_)) auch ganz gut. Allerdings habe ich ein Logikproblem, habe nun festgestellt dass es bei krummen Datentypen (ungerade Bitzahl) doch nicht so funktioniert wie ich dachte.

funktioniert (das __attribute__((_packed_)) bitte dazu denken)

1
struct  {
2
  bool bit7     : 1;  // OFFSET 7, LENGTH 1
3
  bool bit6     : 1;  // OFFSET 6, LENGTH 1
4
  bool bit5     : 1;  // OFFSET 5, LENGTH 1
5
  bool bit4     : 1;  // OFFSET 4, LENGTH 1
6
  bool bit3     : 1;  // OFFSET 3, LENGTH 1
7
  bool bit2     : 1;  // OFFSET 2, LENGTH 1
8
  bool bit1     : 1;  // OFFSET 1, LENGTH 1
9
  bool bit0     : 1;  // OFFSET 0, LENGTH 1
10
} Input;
11

12
memcpy(&Input, Data, len);

funktioniert auch, wenn man das struct byteweise aufteilt

1
struct  {
2
// byte 1
3
  bool bit7     : 1;  // OFFSET 7, LENGTH 1
4
  bool bit6     : 1;  // OFFSET 6, LENGTH 1
5
  bool bit5     : 1;  // OFFSET 5, LENGTH 1
6
  bool bit4     : 1;  // OFFSET 4, LENGTH 1
7
  bool bit3     : 1;  // OFFSET 3, LENGTH 1
8
  bool bit2     : 1;  // OFFSET 2, LENGTH 1
9
  bool bit1     : 1;  // OFFSET 1, LENGTH 1
10
  bool bit0     : 1;  // OFFSET 0, LENGTH 1
11
// bytes 2, 3, 4
12
  uint8_t var1  : 8;  // OFFSET 8, LENGTH 8
13
  uint8_t var2  : 8; // OFFSET 16, LENGTH 8
14
  uint8_t var3  : 8; // OFFSET 24, LENGTH 8
15
// byte 5
16
  uint8_t var4  : 3; // OFFSET 37, LENGTH 3
17
  bool bit36    : 1; // OFFSET 36, LENGTH 1
18
  bool bit35    : 1; // OFFSET 35, LENGTH 1
19
  bool bit34    : 1; // OFFSET 34, LENGTH 1
20
  bool bit33    : 1; // OFFSET 33, LENGTH 1
21
  bool          : 1;
22
// byte 6
23
  uint8_t var5  : 8; // OFFSET 40, LENGTH 8
24
// byte 7
25
  uint8_t var66 : 2; // OFFSET 54, LENGTH 2
26
  uint8_t var64 : 2; // OFFSET 52, LENGTH 2
27
  uint8_t var62 : 2; // OFFSET 50, LENGTH 2
28
  bool          : 1;
29
  bool bit48    : 1; // OFFSET 48, LENGTH 1
30
// byte 8
31
  uint8_t var7  : 8; // OFFSET 56, LENGTH 8
32
} Input2;
33

34
memcpy(&Input2, Data, len);

funktioniert bedingt, aber var1 + var3 benötigen nachher noch Konvertierung little endian <-> big endian. lässt sich lösen, kein Problem.

1
struct  {
2
  uint8_t var0  : 8; //  OFFSET 0, LENGTH 8
3
  uint16_t var1 :16; //  OFFSET 8, LENGTH 16
4
  uint16_t var3 :16; // OFFSET 24, LENGTH 16
5
  uint8_t var5  : 8; // OFFSET 40, LENGTH 8
6
} Input3;
7

8
memcpy(&Input3, Data, len);

aber

1
struct  {
2
// byte 1
3
  uint8_t       : 3;
4
  bool bit4     : 1;  // OFFSET 4, LENGTH 1
5
  bool bit3     : 1;  // OFFSET 3, LENGTH 1
6
  bool bit2     : 1;  // OFFSET 2, LENGTH 1
7
  bool bit1     : 1;  // OFFSET 1, LENGTH 1
8
  bool bit0     : 1;  // OFFSET 0, LENGTH 1
9
// byte 2
10
  uint8_t       : 3;
11
  bool bit12    : 1; // OFFSET 12, LENGTH 1
12
  bool bit11    : 1; // OFFSET 11, LENGTH 1
13
  bool bit10    : 1; // OFFSET 10, LENGTH 1
14
  bool bit9     : 1;  // OFFSET 9, LENGTH 1
15
  bool bit8     : 1;  // OFFSET 8, LENGTH 1
16
// byte 3
17
  bool bit23    : 1; // OFFSET 23, LENGTH 1
18
  bool bit22    : 1; // OFFSET 22, LENGTH 1
19
  uint8_t       : 6;
20
// byte 4
21
  bool bit28    : 1; // OFFSET 28, LENGTH 1
22
  bool bit27    : 1; // OFFSET 27, LENGTH 1
23
  bool bit26    : 1; // OFFSET 26, LENGTH 1
24
  bool bit25    : 1; // OFFSET 25, LENGTH 1
25
  bool bit24    : 1; // OFFSET 24, LENGTH 1
26
// byte 4 (rest) + byte 5
27
  uint16_t var5 :11; // OFFSET 29, LENGTH 11
28
} Input4;
29

30
memcpy(&Input4, Data, len);

funktioniert nicht. für bit24 ... bit28 oder var5 völlig falsche Bits. die bools sind versetzt, lässt sich lösen. aber keinen blassen Schimmer wie man var5 füllt (und endianess korrigiert). Ich brauche aber die structs für den Zugriff = Input4.var5

Außerdem sind die structs autogeneriert, daher müsste ich später ein neues Script schreiben. Aber erstmal brauche ich die Logik, selbst wenn ich es mit Bitmasken und Verschiebung machen würde ist es mir nicht klar welche Bits wohin kommen. Kann jemand das Muster erkennen?

#7494718
Lesenswert?

hier ein Beispiel:

Data (Bit 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48)

Input3.var0 (Bit 1, 2, 3, 4, 5, 6, 7, 8)

Input3.var1 (Bit 17, 18, 19, 20, 21, 22, 23, 24, 9, 10, 11, 12, 13, 14, 15, 16)

Input3.var3 (Bit 33, 34, 35, 36, 37, 38, 39, 40, 25, 26, 27, 28, 29, 30, 31, 32)

Input3.var5 (Bit 41, 42, 43, 44, 45, 46, 47, 48)

gleiche Logik sollte für Input4 gelten, aber ich komm nicht drauf.

#7494732
Lesenswert?

Ich habe immer nur StructName + MemberName + MemberOffset + MemberLänge in Textform gegeben, keine konkreten Infos über den Datentyp. Das können ganze Bytes sein oder Bitfelder. Sollte doch möglich sein anhand der ersten drei Beispiele die Logik für das vierte abzuleiten. Das Muster ist erkennbar, an den krummen Datentypen scheitert die Anwendung. Die Endianess könnte auch andersrum sein, war nur geraten.

#7496995
Lesenswert?

Ich habe jetzt var5 geteilt, damit werden die Bits korrekt zugeordnet und die gleiche Logik angewendet wie in den anderen drei Beispielen.

Nur wusste ich nicht wie man den Split synchronisieren kann. Ich habe mich bei ChatGPT angemeldet, heraus kam das.

Eine Überladung für das Schreiben, und eine für das Lesen.

Nun könnte ich ja aber mehrere uint16_t in dem Struct haben, und ChatGPT konnte mir nicht die Frage beantworten wie ich die zweite Überladung spezifisch für var5 definieren kann.

1
// byte 4
2
  uint8_t rest4 : 3;
3
  bool bit28    : 1; // OFFSET 28, LENGTH 1
4
  bool bit27    : 1; // OFFSET 27, LENGTH 1
5
  bool bit26    : 1; // OFFSET 26, LENGTH 1
6
  bool bit25    : 1; // OFFSET 25, LENGTH 1
7
  bool bit24    : 1; // OFFSET 24, LENGTH 1
8

9
  uint8_t byte5 : 8;
10

11
// byte 4 (rest) + byte 5
12
  uint16_t var5 :11; // OFFSET 29, LENGTH 11
13

14
  // Überladung des Zuweisungsoperators "=" für var5
15
  Input4& operator=(const uint16_t value) {
16
    var5 = value;
17
    rest4 = (value >> 8) & 0b00000111;
18
    byte5 = value & 0b11111111;
19
    return *this;
20
  }
21
  // Überladung des Type Casting Operators für uint16_t
22
  operator uint16_t() const {
23
    var5 = (rest4 << 8) | byte5;
24
  }
25
} Input4;

Könnte man einfach eine weitere Zeile einfügen var7 = (rest6 << 8) | byte7; und was nehme ich dann als Return-Wert?

P.S. und bevor die Frage kommt, das brauche ich dann zum Schluss auch noch (oder ein union)

Wilhelm M. schrieb:

In dem Funktionstemplate bit<>() benutzt man natürlich Shifts statt eine union.

#7497066
Lesenswert?

Alexander schrieb:

Nun könnte ich ja aber mehrere uint16_t in dem Struct haben, und ChatGPT konnte mir nicht die Frage beantworten wie ich die zweite Überladung spezifisch für var5 definieren kann.

Zwar weiß ich immer noch überhaupt nicht was Du erreichen willst bzw. welche Bytes wohin sollen (s.o. meine Frage: Du meintest, man müsste ein Muster erkennen ...).

Jedoch: überladen kann man nur, wenn die Signatur der Funktion (Name und Parameterliste) sich unterscheidet, Rückgabetyp der Funktion gehört (bei C++) nicht zur Signatur. Warum Du allerdings den Kopierzuweisungsoperator in der Art überladen willst, ist mir schleierhaft.

Die Bytes werden ja eine Bedeutung haben, daher wären geeignet benannte non-const Funktionen (aka: Mutatoren) besser:

1
struct Input {
2
    auto& foo(const uint16_t v) {...}
3
    auto& bar(const uint16_t v) {...}
4
};
5
...
6
Input i;
7
i.foo(42).bar(13); // <1>

statt

1
i = 42; // <2>

Denn bei <1> erkennt man sofort, was los ist, während man bei <2> rätselt oder eben das falsche vermutet.

Alexander schrieb:

P.S. und bevor die Frage kommt, das brauche ich dann zum Schluss auch noch (oder ein union)

Wilhelm M. schrieb:

In dem Funktionstemplate bit<>() benutzt man natürlich Shifts statt eine union.

Wie schon gesagt: Du möchtest über das eine Element der union einen Wert zuweisen. Dies ist dann das sog. "active-member". Dann möchtest Du von einem anderen Element lesen, was nicht das "active-member" ist: das ist in i.a. C++ UB (in C hingegen explizit erlaubt: type-punning).

Den Rest Deines Beitrages verstehe ich leider wieder gar nicht.

Moderator Persönliche Seite #7497152
Lesenswert?

Interessant, was hier für ein Aufwand getrieben wird, um Bitfelder zu retten.

@TO:

Schmink Dir die Bitfelder einfach ab. Das Zeugs ist höchst unportabel. Es gab vielleicht in den 70er Jahren mal einen Grund dafür, nämlich für schlecht optimierende C-Compiler höchst effizienten Code zu schreiben. Diese Zeiten sind aber längst vorbei.

Verwende von vornherein ein Byte und maskiere die gewünschten Bits, die Du brauchst. Das ist trivial und funktioniert auf jeder Plattform gleich. Ein C-Compiler wird Deine Masken-Operationen analog zu Bitfeldern umsetzen. Dabei entsteht im Vergleich zu Bitfeldern keine Performance-Einbuße. Gleichzeitig hast Du aber die Garantie der Portabilität.

Beispiel:

1
  uint8_t value = 0;
2

3
  value |= (1<<3);     // Bit 3 setzen
4
  value &= ~(1<<3);    // Bit 3 löschen
5
  if (value & (1<<3))  // Auf Bit 3 testen

Wenn Du den Bits auch noch Namen gibst, wird es noch klarer. Hier ein Beispiel:

1
#define LED_MASK_RED   (1<<2) // Maske fuer rote LED, an Pin#2
2
#define LED_MASK_GREEN (1<<3) // Maske fuer gruene LED, an Pin#3
3
...
4
  uint8_t led = 0;
5

6
  led |= LED_MASK_RED;       // rote LED setzen
7
  led &= ~LED_MASK_GREEN;    // grüne LED löschen
8
  outport (led);             // Das Byte an Port ausgeben
#7497270
Lesenswert?

Wilhelm M. schrieb:

Zwar weiß ich immer noch überhaupt nicht was Du erreichen willst bzw. welche Bytes wohin sollen (s.o. meine Frage: Du meintest, man müsste ein Muster erkennen ...).

Im Gegensatz zum TE der nur lesen möchte, möchte ich Data in ein Struct schreiben. Aktuell klappt das ganz gut mit memcpy() aber da kannte ich Union noch nicht.

Das Muster hast Du ja schon genannt, nur dass es eben nicht immer ganze Bytes sind.

Wilhelm M. schrieb:

var0 = Byte0 var1 = [high = Byte2, low = Byte1]; var3 = [high = Byte4, low = Byte3]; var5 = Byte5

Manchmal hat eine Variable eine krumme Anzahl Bits. Das habe ich nun so gelöst, indem ich die Variable in zwei 8 Bit Variablen gesplittet habe. Am Ende des Structs habe ich nun eine zusätzliche Variable eingefügt.

Frank M. schrieb:

Wenn Du den Bits auch noch Namen gibst, wird es noch klarer. Hier ein Beispiel:

1
> #define LED_MASK_RED   (1<<2) // Maske fuer rote LED, an Pin#2
2
> #define LED_MASK_GREEN (1<<3) // Maske fuer gruene LED, an Pin#3
3
>

Mir ist noch nicht ganz klar wie ich das in die Structs reinkriege. Ich brauche ja eine Gruppierung.

Wilhelm M. schrieb:

Warum Du allerdings den Kopierzuweisungsoperator in der Art überladen willst, ist mir schleierhaft.

Ich habe noch nie zuvor von einer Überladung gehört und musste ChatGPT auch erst mal befragen. Die zweite Überladung würde angeblich jedesmal ausgeführt wenn ich value = Input4.var5; irgendwo auslese, da beim Lesen angeblich intern immer ein Typecast ausgeführt würde. Die erste Überladung würde bei jedem Schreiben ausgeführt, Input4.var5 = 0b11111111111; so dass im Endeffekt immer mit Input4.rest4 = 0b111; Input4.byte5 = 0b11111111; in beide Richtungen synchronisiert würde.

Von außen sollte sich das ganze wie ein normales Struct verhalten, wobei mich außen immer nur Input4.var5 interessiert, und innen immer nur Input4.rest4 + Input4.byte5. Input4.var5 steht aber zusätzlich am Ende des Structs und ist kein Teil von Data. Getestet habe ich noch nicht.

Wilhelm M. schrieb:

Den Rest Deines Beitrages verstehe ich leider wieder gar nicht.

Ich werde nun wieder ChatGPT befragen da ich Deinen Vorschlag mit Mutatoren auch nicht verstehe (trotz Beispielcode) aber vielleicht ist das ja das entscheidende Keyword. ChatGPT wird ja immer gelobt es könne gut mit Programmiersprachen umgehen, ist eine Verzweiflungstat.

Harald K. schrieb:

Welches Problem soll hier eigentlich gelöst werden? Woher kommen die Daten, was soll mit ihnen geschehen?

Die Daten kommen vom CAN-Bus eines Fahrzeuges. Data ist immer die CAN Message, jeweils mit unterschiedlichen Längen. Je nach CAN-ID soll Data in ein anderes Struct kopiert werden. Das ganze Arduino-Nutzer-freundlich, und bidirektional.

Hier sind vier Beispiele für vier verschiedene CAN Messages, mit unterschiedlichen Längen. Beitrag "Re: Hilfe um 8 Bitfelder in uint8_t umzuwandeln (ESP32)"

Der Clou soll sein, dass man sich überhaupt nicht um CAN Message und Aufbau kümmern muss, und auch nicht wie man ein Struct füllt. Es wird einfach die Header-Datei eingebunden und ein Struct mit memcpy() gefüllt (oder mit Zuweisung Input = Data; wenn das geht)

Evtl. könnte ich das auch noch vereinfachen, indem ich gleich die CAN Message komplett als erstes Member in das Struct mit aufnehme. Und die Verteilung der Bitfelder erfolgt dann komplett mit Shifts und Maskierung innerhalb des Structs, da könnte ich auf memcpy() verzichten. Wichtig bleibt am Ende der Zugriff über die Klarnamen der Bits Input.LED_GREEN.

Das ganze ist aber nur ein Teilproblem, da die Structs selbst auch nur generiert sind. Ich arbeite an einem Script dass die Stucts erstellt. Ich bin mir nicht sicher ob es zielführend wäre hier so tief in das Problem einzusteigen.

Bisher hat es so gut funktioniert, nun brauche ich aber das erste mal einen dieser krummen Datentypen (ungerade Bitzahl) da ist mir der Fehler erst aufgefallen.

#7497368
Lesenswert?

Alexander schrieb:

Wilhelm M. schrieb:

Zwar weiß ich immer noch überhaupt nicht was Du erreichen willst bzw. welche Bytes wohin sollen (s.o. meine Frage: Du meintest, man müsste ein Muster erkennen ...).

Das Muster hast Du ja schon genannt, nur dass es eben nicht immer ganze Bytes sind.

Geht es um die Payload eines CAN-BUS Pakets? Dann beschreibe doch mal den Aufbau.

Wilhelm M. schrieb:

var0 = Byte0 var1 = [high = Byte2, low = Byte1]; var3 = [high = Byte4, low = Byte3]; var5 = Byte5

Manchmal hat eine Variable eine krumme Anzahl Bits. Das habe ich nun so gelöst, indem ich die Variable in zwei 8 Bit Variablen gesplittet habe. Am Ende des Structs habe ich nun eine zusätzliche Variable eingefügt.

Wie Du erkannt hast, ist das Hantieren mit einer "krummen" Anzahl von Bits unpraktisch. Also schreibt man sich ein (oder mehrere Klassen), an die man die jeweils relevanten Daten zum "Auseinandernehmen" mit Bit-Shift oder einfachem Maskieren übergibt.

Wilhelm M. schrieb:

Warum Du allerdings den Kopierzuweisungsoperator in der Art überladen willst, ist mir schleierhaft.

Ich habe noch nie zuvor von einer Überladung gehört und musste ChatGPT auch erst mal befragen.

Dann hast Du noch nie in C++ programmiert. Dann ist aber dieses Projekt kein guter Startpunkt, damit anzufangen.

Bleibe bei reinem C (egal ob als C oder C++-Code).

Das spielt aber auch gar keine Rolle: statt Elementfunktionen aufzurufen, schreibst Du einfach weiterhin (freie) Funktionen und übergibst die Objekte (ggf. dann als Output-Parameter).

Wilhelm M. schrieb:

Den Rest Deines Beitrages verstehe ich leider wieder gar nicht.

Ich werde nun wieder ChatGPT befragen da ich Deinen Vorschlag mit Mutatoren auch nicht verstehe (trotz Beispielcode) aber vielleicht ist das ja das entscheidende Keyword.

Non-const Elementfunktionen (aka Mutatoren) sind im Gegensatz zu const-Elementfunktionen welche, die den Objektzustand ändern können.

Aber wie gesagt: die Frage zeigt auch wieder, dass Du keine Ahnung von C++ hast. Deswegen bleibe bei C (s.o.).

ChatGPT wird ja immer gelobt es könne gut mit Programmiersprachen umgehen, ist eine Verzweiflungstat.

Ja, machst es nicht besser.

Harald K. schrieb:

Welches Problem soll hier eigentlich gelöst werden? Woher kommen die Daten, was soll mit ihnen geschehen?

Die Daten kommen vom CAN-Bus eines Fahrzeuges. Data ist immer die CAN Message, jeweils mit unterschiedlichen Längen. Je nach CAN-ID soll Data in ein anderes Struct kopiert werden. Das ganze Arduino-Nutzer-freundlich, und bidirektional.

Nochmal: geht es um die Payload oder um das gesamte Paket? Hört sich so an, als hättest Du keine CAN-Bus-Controller.

Der Clou soll sein, dass man sich überhaupt nicht um CAN Message und Aufbau kümmern muss, und auch nicht wie man ein Struct füllt.

Ja, je nach CAN-ID und unterschiedlciher PayLoad nimmst Du eine andere Klasse.

Das ganze ist aber nur ein Teilproblem, da die Structs selbst auch nur generiert sind. Ich arbeite an einem Script dass die Stucts erstellt. Ich bin mir nicht sicher ob es zielführend wäre hier so tief in das Problem einzusteigen.

Welche Art von PDL ist das denn? Oder machst Du das zu Fuß?

#7497389
Lesenswert?

Wilhelm M. schrieb:

Geht es um die Payload eines CAN-BUS Pakets? Dann beschreibe doch mal den Aufbau.

Ja. Es gibt nicht "den" Aufbau. Vier Beispiele habe ich gezeigt. Hier geht es aber nur darum, auf alle Bits eines Bitfelds gleichzeitig zuzugreifen.

Wilhelm M. schrieb:

Dann hast Du noch nie in C++ programmiert.

richtig. Ich nutze Arduino Libraries.

Wilhelm M. schrieb:

Ja, je nach CAN-ID und unterschiedlicher PayLoad nimmst Du eine andere Klasse.

Könnte schwierig werden in reinem C. aktuell nutze ich Strukturen.

Wilhelm M. schrieb:

Welche Art von PDL ist das denn?

keine.

Wilhelm M. schrieb:

Oder machst Du das zu Fuß?

Ja. mit shell script. oder Python.

#7497405
Lesenswert?

Alexander schrieb:

Ja. Es gibt nicht "den" Aufbau. Vier Beispiele habe ich gezeigt. Hier geht es aber nur darum, auf alle Bits eines Bitfelds gleichzeitig zuzugreifen.

Du hast die Payload als Array von Bytes (uint8_t) nehme ich an.

In C++ würde man nun das ganze Array einem Konstruktor einer Klasse übergeben, und der Konstruktor (oder eine andere Elementfunktion) popelt aus den vielen Bytes der Payload durch Shifts und Maskieren und ggf. wieder zu größeren Datentypen wie Zusammenfügen die einzelnen Elemente heraus.

Das kann man natürlich auch in C durch eine Initialisierungsfunktion machen:

1
typedef struct Data1 {
2
    uint16_t temperature;
3
    uint8_t sensor;
4
    ...
5
} data1_t;
6
typedef struct Data2 {
7
    ...
8
} data2_t;
9

10
void initData(data_t* const d, const uint8_t* const msg, const uint8_t length) {
11
    if (msg[0] == 0x01) {
12
        data1_t* const data = (data1_t*)d;
13
        data->temperature = ((uint16_t)msg[4] << 3) | ((msg[5] & 0xe0) >> 5);
14
        data->sensor = msg[6] & 0x0f;
15
    }
16
    else if (msg[0] == 0x04) {
17
        data2_t* const data = (data2_t*)d;
18
        ...
19
    }
20
    ...
21
}

Das obiger soll nur das Prinzip zeigen. Ob du das dispatch in der Funktion oder außerhalb machst, musst Du entscheiden, wo es sinnvoller ist.

Natürlich rate ich Dir, statt reines C ein rein-prozedurales C++ mit den C++-Goodies zu verwenden: also, statt rohes C-Arrays etwa std::array<>, ...

#7497438
Lesenswert?

Alexander schrieb:

Eine Initialisierungsfunktion brauche ich schon noch, nur soll diese nicht viel machen. Das war ja der Sinn der Structs, die Arbeit abzunehmen. Da soll nur rein:

1
>   switch(id) {
2
>     case CANID:
3
> //      memcpy(&Input, Data, len);
4
>       Input = Data; // mit Union?
5
>       break;
6
>   }
7
>

Wir drehen uns im Kreis. Ja sicher, das geht in C. Es ist aber gehupft wie gesprungen - und es bleibt nicht portabel. Besser mit Shifts, ...

#7497510
Lesenswert?

Alexander schrieb:

Der Kreis schließt sich hier. Weiß nur nicht wie man das in ein Struct packt. Dachte Dein "Funktionstemplate bit<>()" wäre die Lösung. höhere C++ Kunst und so.. Beitrag "Re: Hilfe um 8 Bitfelder in uint8_t umzuwandeln (ESP32)"

Aber auch dafür musst Du wissen, wohin(!) jedes Bit abgebildet werden soll. Dieses Wissen scheinst Du aber nicht zu haben.

#7497566
Lesenswert?

Wilhelm M. schrieb:

Aber auch dafür musst Du wissen, wohin(!) jedes Bit abgebildet werden soll. Dieses Wissen scheinst Du aber nicht zu haben.

Doch, habe ich. Ich möchte nur wissen wie ich es umsetze. Die Überladung funktioniert nicht (wird nicht wie behauptet ausgeführt). Wenn ich es in die Initialisierungsfunktion packe funktioniert es. Ich will aber eine Funktion im Struct.

#7497604
Lesenswert?

Alexander schrieb:

Die Überladung funktioniert nicht (wird nicht wie behauptet ausgeführt). Wenn ich es in die Initialisierungsfunktion packe funktioniert es.

Du meinst, einen Kontruktor? Oder eine freie Funktion? Beides geht mit Shifts. Wie schon mehrfach gesagt, ist das Verwenden von unions wie Du es willst in C++ UB.

Ich will aber eine Funktion im Struct.

Du bist bemerkenswert beratungsresistent: wenn Du damit meinst, dass Du in C++ eine Elementfunktion benutzen möchtest zusammen mit Deinem union Ansatz: es ist UB!

Ich hatte Dir oben einen Ansatz mit Shift, Mask, Compose ausformuliert: benutze den.

#7497607
Lesenswert?

Klaus H. schrieb:

Wilhelm M. schrieb:

In dem Funktionstemplate bit<>() benutzt man natürlich Shifts statt eine union.

Kurze Frage: Ich kenn nur std::bitset<N> aus dem <bitset> Header.

Ist unbenutzbar für diesen Zweck, weil keine Garantien über das Layout möglich sind.

Dort gibt es IMHO aber kein bit<x>(v) sonder Zugriff Über v[x]. Welches Modul meinst Du?

Gar kein Modul aus der stdlibc++.

Ich hatte mir in etwa folgendes vorgestellt:

1
// -->: in eigene Library-Header
2
template<uint8_t N, typename T> requires (N < (sizeof(T) * 8)) auto bit(T& v);
3

4
namespace detail {
5
    template<uint8_t N, typename T> requires (N < (sizeof(T) * 8))
6
    struct Bit {
7
        friend auto bit<N, T>(T& v);
8

9
        template<typename B> requires (std::is_same_v<B, bool>)
10
        void operator=(const B b) {
11
            if (b) {
12
                d = d | std::remove_cvref_t<T>(1U << N);
13
            }
14
            else {
15
                d = d & std::remove_cvref_t<T>(~(1U << N));
16
            }
17
        }
18
    private:
19
        explicit Bit(T& v) : d{v}{}
20
        T& d;
21
    };
22
}
23

24
template<uint8_t N, typename T> requires (N < (sizeof(T) * 8))
25
auto bit(T& v) {
26
    return detail::Bit<N, T>{v};    
27
}
28

29
// <-- Ende Library-Header
30

31
// Beispiel zur Verwendung
32
std::byte v;
33

34
int main() {    
35
    bit<3>(v) = true;
36
    bit<7>(v) = false;
37
    
38
    // folgendes ist nicht möglich
39
    // bit<8>(v) = false; 
40
    // bit<0>(v) = 1;
41
}
#7497817
Lesenswert?

Wilhelm M. schrieb:

Ich hatte Dir oben einen Ansatz mit Shift, Mask, Compose ausformuliert: benutze den.

Meinst du deine Funktion initData()? Davon rede ich doch. Wie? Die ist doch in main() manuell ausgeführt. Ich brauche es aber automatisch, und die Funktion soll in das Struct verschoben werden.

So funktioniert es derzeit. Logik gefunden (BIG ENDIAN)

1
  ...
2
  uint8_t rest4 : 3;
3
  bool bit28    : 1; // OFFSET 28, LENGTH 1
4
  bool bit27    : 1; // OFFSET 27, LENGTH 1
5
  bool bit26    : 1; // OFFSET 26, LENGTH 1
6
  bool bit25    : 1; // OFFSET 25, LENGTH 1
7
  bool bit24    : 1; // OFFSET 24, LENGTH 1
8

9
  uint8_t byte5 : 8; // Ende vom Struct
10

11
  // wird nicht von memcpy() gefüllt
12
  uint16_t var5 :11; // OFFSET 29, LENGTH 11
13
} Input4;
14

15
void initData(unsigned int id, const uint8_t *Data, uint8_t len) {
16
  switch(id) {
17
    case CANID:
18
      memcpy(&Input4, Data, len);
19
      Input4.var5 = (Input4.rest4 << 8) | Input4.byte5; // <-- DAS
20
      break;
21
  }
22
}
23
void loop() {
24
  initData(msg.id, msg.buf, msg.len);
25
  if ( Input4.var5 == 2047 ) {
26
    ...
27
  }
28
  Input4.var5 = 1024;
29
  Input4.rest4 = (Input4.var5 >> 8) & 0b00000111; // <--
30
  Input4.byte5 = Input4.var5 & 0b11111111;
31
}

So soll es werden. Auf das Union kann ich auch verzichten, aber dann brauche ich memcpy() oder ich nehme Data komplett in jedes Struct mit auf. Ob es nun eine Klasse oder Struct wird, Elementfunktion, Kontruktor oder Mutator heißt ist mir egal, Hautpsache die Synchronisation wird automatisch ausgeführt. Und was UB heißt weiß ich nicht, das ist alles fachchinesisch. Es muss nicht portabel sein.

1
  ...
2
  uint8_t rest4 : 3;
3
  bool bit28    : 1; // OFFSET 28, LENGTH 1
4
  bool bit27    : 1; // OFFSET 27, LENGTH 1
5
  bool bit26    : 1; // OFFSET 26, LENGTH 1
6
  bool bit25    : 1; // OFFSET 25, LENGTH 1
7
  bool bit24    : 1; // OFFSET 24, LENGTH 1
8

9
  uint8_t byte5 : 8;
10

11
  uint16_t var5 :11; // OFFSET 29, LENGTH 11
12

13
  Input4& operator==(const uint16_t value) {
14
      var5 = (rest4 << 8) | byte5; // <-- SOLL HIER HIN
15
    return *this;
16
  }
17
  Input4& operator=(const uint16_t value) {
18
    var5 = value;
19
    rest4 = (value >> 8) & 0b00000111; // <--
20
    byte5 = value & 0b11111111;
21
    return *this;
22
  }
23
} Input4;
24

25
void initData(unsigned int id, const uint8_t *Data, uint8_t len) {
26
  switch(id) {
27
    case CANID:
28
      memcpy(&Input4, Data, len);
29
      // hier soll nichts weiter stehen
30
      break;
31
  }
32
}
33
void loop() {
34
  initData(msg.id, msg.buf, msg.len);
35
  if ( Input4.var5 == 2047 ) {
36
    ...
37
  }
38
  Input4.var5 = 1024;
39
  // hier auch nicht
40
}
#7497825
Lesenswert?

Alexander schrieb:

Input4& operator==(const uint16_t value) { var5 = (rest4 << 8) | byte5; // <-- SOLL HIER HIN return *this; }

Das ist kein Zuweisungsoperator, sondern ein Vergleichsoperator.

Mir scheint, Du versuchst mit try-n-error zum Ziel zu kommen, aber das wird Dir nicht gelingen.

Und nochmals: das Du ja C++ programmierst ("Arduino" ist C++), vergiss den Ansatz über die union, das ist UB (undefined behaviour).

Versuche:

1
struct Input {
2
    Input(const uint8_t id, const uint8_t* const msg, const uint8_t length) {
3
        // ... hier mit Shift, Mask, ... aus den Bytes von msg die Werte herausholen
4
    }
5
private:
6
    uint8_t var1;
7
    uint16_t var2;
8
};
#7497831
Lesenswert?

Alexander schrieb:

Wilhelm M. schrieb:

Das ist kein Zuweisungsoperator, sondern ein Vergleichsoperator.

yep. der ist zum Lesen, nicht zum Schreiben. siehe hier.

1
if ( Input4.var5 == 2047 ) {

Nö.

Wilhelm M. schrieb:

Mir scheint, Du versuchst mit try-n-error zum Ziel zu kommen, aber das wird Dir nicht gelingen.

Was wäre die Alternative? Ein Buch lesen? Im Forum fragen?

Dir die Grundlagen von C++ aneignen. Buch macht kluch.

#7497838
Lesenswert?

Wilhelm M. schrieb:

Versuche:

1
struct Input {
2
    Input(const uint8_t id, const uint8_t* const msg, const uint8_t 
3
length) {
4
        // ... hier mit Shift, Mask, ... aus den Bytes von msg die Werte 
5
herausholen
6
    }
7
private:
8
    uint8_t var1;
9
    uint16_t var2;
10
};

Das sieht doch gut aus. Dann kann ich das memcpy ersetzen mit einer Zeile wo ich das aufrufe. Nur brauche ich es bidirektional. Aber so komme ich erstmal weiter, danke.

Joe L. schrieb:

Was die Abbreviatur "UB" genau heißt -- das musst Du unstreitig nicht wissen. Vereinfacht gesagt hat das was mit 'Lernresistenz' zu tun. Näheres hierzu siehe https://www.stupidedia.org/stupi/Lernresistenz ...

Und was heißt UB (undefined behaviour) nun? Dass es auf meinem Arduino Teensy oder ESP32 funktioniert, aber auf deinem Compiler nicht? Spielt wie gesagt keine Rolle, da es für Arduino ist. Ich schrieb, es muss nicht portabel sein.

Die Bitfelder hatte ich übrigens ursprünglich doppelt generieren lassen, habe ich dann aber wegoptimiert da nicht benötigt.

1
typedef struct Input_t {
2
  #if __BYTE_ORDER == __LITTLE_ENDIAN
3
    bool bit0 : 1;
4
    bool bit1 : 1;
5
    bool bit2 : 1;
6
    bool bit3 : 1;
7
  #elif __BYTE_ORDER == __BIG_ENDIAN
8
    bool bit3 : 1;
9
    bool bit2 : 1;
10
    bool bit1 : 1;
11
    bool bit0 : 1;
12
  #endif
13
} __attribute__((__packed__)) Input_t;
#7497857
Lesenswert?

Alexander schrieb:

Wilhelm M. schrieb:

Versuche:

1
struct Input {
2
>     Input(const uint8_t id, const uint8_t* const msg, const uint8_t
3
> length) {
4
>         // ... hier mit Shift, Mask, ... aus den Bytes von msg die Werte
5
> herausholen
6
>     }
7
> private:
8
>     uint8_t var1;
9
>     uint16_t var2;
10
> };

Das sieht doch gut aus. Dann kann ich das memcpy ersetzen mit einer Zeile wo ich das aufrufe.

Das nennt man dann Konstruktor ;-)

Etwas besser wäre dann noch:

1
struct Input {
2
    template<auto N>
3
    Input(const uint8_t id, const std::array<uint8_t, N>& msg) {}
4
};

Dann bist Du diese rohen C-Arrays los.

Nur brauche ich es bidirektional. Aber so komme ich erstmal weiter, danke.

Dann ggf. so:

1
struct Input {
2
    template<auto N>
3
    Input(const uint8_t id, const std::array<uint8_t, N>& msg) {}
4

5
    auto toArray() const {
6
        std::array<uint8_t, 42> data;
7
        ...
8
        return data;
9
    }
10
};

Joe L. schrieb:

Was die Abbreviatur "UB" genau heißt -- das musst Du unstreitig nicht wissen. Vereinfacht gesagt hat das was mit 'Lernresistenz' zu tun. Näheres hierzu siehe https://www.stupidedia.org/stupi/Lernresistenz ...

Und was heißt UB (undefined behaviour) nun?

Ist Google mal wieder kaputt?

Dass es auf meinem Arduino Teensy oder ESP32 funktioniert, aber auf deinem Compiler nicht? Spielt wie gesagt keine Rolle, da es für Arduino ist. Ich schrieb, es muss nicht portabel sein.

Das es nirgendwo eine Garantie gibt, dass es funktioniert.

Die Bitfelder hatte ich übrigens ursprünglich doppelt generieren lassen, habe ich dann aber wegoptimiert da nicht benötigt.

Besser ist es: s.o. union und UB.

#7497906
Lesenswert?

Arm schrieb:

In C++20 ist std::bit_cast constexpr, damit kann man per static_assert prüfen, ob die bits in einem bitfield so angelegt sind, wie man es erwartet.

Das ist zwar richtig und sinnvoll, jedoch beweifel ich auch hier, dass der TO überhaupt etwas damit anfangen kann, oder weiß, was ein constexpr-Kontext ist. Und streng genommen müsste er sich die Arbeit dann zweimal machen ...

#7497965
Lesenswert?

Ja das ging nicht, ich glaube Arduino 1.8.9 ist C++17 oder C++11. Ich hab das nun mal für das erste Beispiel so übernommen. Muss ich noch testen.

1
typedef struct message_t {
2
  uint32_t id = 0;
3
  uint8_t len = 8;
4
  uint8_t buf[8] = { 0 };
5
} message_t;
6

7
typedef struct Input_t {
8
  template<auto N>
9
  receive(const std::array<uint8_t, N>& msg) {
10
    bit0 = (msg[0] >> 0U) & 1U;
11
    bit1 = (msg[0] >> 1U) & 1U;
12
    bit2 = (msg[0] >> 2U) & 1U;
13
    bit3 = (msg[0] >> 3U) & 1U;
14
    bit4 = (msg[0] >> 4U) & 1U;
15
    bit5 = (msg[0] >> 5U) & 1U;
16
    bit6 = (msg[0] >> 6U) & 1U;
17
    bit7 = (msg[0] >> 7U) & 1U;
18
  }
19
  auto transmit() const {
20
    std::array<uint8_t, 8> data;
21
    data[0] = ((uint8_t)(bit0) << 0U) |
22
              ((uint8_t)(bit1) << 1U) |
23
              ((uint8_t)(bit2) << 2U) |
24
              ((uint8_t)(bit3) << 3U) |
25
              ((uint8_t)(bit4) << 4U) |
26
              ((uint8_t)(bit5) << 5U) |
27
              ((uint8_t)(bit6) << 6U) |
28
              ((uint8_t)(bit7) << 7U);
29
    return data;
30
  }
31
  bool bit0     : 1;  // OFFSET 0, LENGTH 1
32
  bool bit1     : 1;  // OFFSET 1, LENGTH 1
33
  bool bit2     : 1;  // OFFSET 2, LENGTH 1
34
  bool bit3     : 1;  // OFFSET 3, LENGTH 1
35
  bool bit4     : 1;  // OFFSET 4, LENGTH 1
36
  bool bit5     : 1;  // OFFSET 5, LENGTH 1
37
  bool bit6     : 1;  // OFFSET 6, LENGTH 1
38
  bool bit7     : 1;  // OFFSET 7, LENGTH 1
39
} Input_t;
40

41
#define CANID 0x0003
42
struct Input_t Input;
43
struct Input_t Output;
44
struct message_t message;
45
std::array<uint8_t, 8> buf;
46

47
void initData(unsigned int id, const uint8_t *Data, uint8_t len) {
48
  switch(id) {
49
    case CANID:
50
      Input.receive(Data);
51
      break;
52
  }
53
}
54
void readMsg(message_t &msg) {
55
  // read Data
56
  ...
57
  initData(reinterpret_cast <uint32_t> (msg.id), msg.buf, msg.len);
58
}
59
void sendMsg(const message_t &msg) {
60
  // send Data
61
  ...
62
}
63
void loop() {
64
  readMsg(message);
65
  Serial.println("empfange Input.bit0 = ", Input.bit0);
66

67
  Output.receive(Input.transmit());
68
  Output.bit0 = 1;
69
  Serial.println("sende Output.bit0 = ", Output.bit0);
70
  
71
  buf = Output.transmit();
72
  for (int i = 0; i < message.len; i++) {
73
    message.buf[i] = buf[i];
74
  }
75
  sendMsg(message);
76
  delay(200);
77
}

Das ist nur PseudoCode- aber wenn man es noch vereinfachen kann dann bitte her damit. message_t readMsg() sendMsg() sind Vorgabe aus einer Library, da kann ich nichts ändern.

#7498093
Lesenswert?

Alexander schrieb:

Joe L. schrieb:

Was die Abbreviatur "UB" genau heißt -- das musst Du unstreitig nicht wissen. Vereinfacht gesagt hat das was mit 'Lernresistenz' zu tun. Näheres hierzu siehe https://www.stupidedia.org/stupi/Lernresistenz ...

Und was heißt UB (undefined behaviour) nun? Dass es auf meinem Arduino Teensy oder ESP32 funktioniert, aber auf deinem Compiler nicht?

Das heißt, dass Du dich aufführst wie ein Vollpfosten!

Lies selber (https://64.github.io/cpp-faq/undefined-behaviour/): "Undefined Behaviour (also known as UB) occurs when you violate certain language rules. [...] When undefined behaviour occurs, the C and C++ standards do not place any restrictions on what your program might do. In other words, your program may crash, or continue execution, or call some seemingly unrelated piece of code, or print 42 and open xkcd in your web browser [...]"

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren