Verständins Problem mit ENUM in "C"

OP #5465302
Lesenswert?

Hallo liebe C Gemeinde
Ich habe da ein enum i2s_mode_t;


typedef enum {
    I2S_MODE_MASTER = 1,
    I2S_MODE_SLAVE = 2,
    I2S_MODE_TX = 4,
    I2S_MODE_RX = 8,
    I2S_MODE_DAC_BUILT_IN = 16,       /*!< Output I2S data to built-in 
DAC, no matter the data format is 16bit or 32 bit, the DAC module will 
only take the 8bits from MSB*/
    I2S_MODE_ADC_BUILT_IN = 32,       /*!< Input I2S data from built-in 
ADC, each data can be 12-bit width at most*/
    I2S_MODE_PDM = 64,
} i2s_mode_t;


i2s_mode_t I2SMode;



I2SMode = I2S_MODE_MASTER | I2S_MODE_TX; // Das geht nicht !!!!!!!

die Orginal Syntax sieht folgendermassen aus


  I2S_Mode  = {
      .mode = I2S_Mode,     // I2S_MODE_MASTER | I2S_MODE_TX, 
// Only TX
    .sample_rate = SAMPLE_RATE,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, 
//16-bit per channel
    .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, 
//2-channels
    .communication_format = I2S_COMM_FORMAT_I2S | 
I2S_COMM_FORMAT_I2S_MSB,
    .dma_buf_count = 6,
    .dma_buf_len = 60, 
//
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1 
//Interrupt level 1
  };

In Pascal macht man Mehrzuweisungen mit einem Set of
http://wiki.freepascal.org/Set

wie macht man das in C
#5465343
Lesenswert?

Das geht deshalb nicht, weil durch die Typdefinition dem Compiler 
explizit mitgeteilt wurde, daß I2SMode nur einen der Werte {1, 2, 4, 16, 
32, 64} annehmen kann.

I2S_MODE_MASTER | I2S_MODE_TX ist 5 und demnach außerhalb des gültigen 
Wertebereichs für diesen Typ.

Mit Pascal kenne ich mich zwar nicht aus, aber dort müsste der Compiler 
ebenfalls meckern, wenn man der Variablen einen Wert außerhalb des 
spezifizierten "Set of" zuweisen will.
Gast #5465368
Lesenswert?

Klar geht das wenn mans richtig hinschreibt oder die Fehlermeldung 
liest:
1
$ cat test.c 
2
typedef enum {
3
    I2S_MODE_MASTER = 1,
4
    I2S_MODE_SLAVE = 2,
5
    I2S_MODE_TX = 4,
6
    I2S_MODE_RX = 8,
7
    I2S_MODE_DAC_BUILT_IN = 16,       /*!< Output I2S data to built-in DAC, no matter the data format is 16bit or 32 bit, the DAC module will only take the 8bits from MSB*/
8
    I2S_MODE_ADC_BUILT_IN = 32,       /*!< Input I2S data from built-in ADC, each data can be 12-bit width at most*/
9
    I2S_MODE_PDM = 64,
10
} i2s_mode_t;
11

12

13
i2s_mode_t I2SMode = I2S_MODE_MASTER | I2S_MODE_TX; // <- desch gehd
14

15

16
void f()
17
{
18
        I2SMode = I2S_MODE_MASTER | I2S_MODE_TX; // <- desch gehd ahh
19
}
20

21
$ gcc -Wall -Wextra -pedantic -std=c99 -c -o test test.c 
22
$ file test
23
test: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

Dass der Wert "zu groß" ist zwar ist formell richtig (aber hier nicht 
das Problem), allerdings gibt es meines Wissens keinen C-Cimpiler auf 
dieser Welt, der das nicht trotzdem richtig machen würde. Wenn man 
standardkonform sein will muss man noch einen Enumerator definieren, in 
den alles rein passt (hier also mindestens den Wert 127 hat).
#5465376
Lesenswert?

(Die 8 habe ich natürlich vergessen! -> {1, 2, 4, 8, 16,
32, 64})

Eine saubere Lösung wäre ggf., im enum alle in Frage kommenden 
Kombinationen zu definieren, also z.B. I2S_MODE_MASTER_TX = 5.
Sonst halt die Variable als standard-Typ deklarieren (z.B. uint8_t) und 
enum nur zur Definition der Bitmasken-Werte benutzen.
OP #5465398
Lesenswert?

Hallo und Danke für die Antworten.
Thomas E.
Du hast natürlich recht, deswegen habe ich ja auch den Link von Set of 
dazugefügt. Dann haben wir eine Menge und können mehrere Type 
Definitionen
auswählen.

Ich kann und will den Source nicht ändern das ist im ESP32 Arduino Core 
so definiert. Der Compiler
meckert mit SP32_I2S.ino: 56:10: error: invalid conversion from 'int' to 
'i2s_mode_t'
Ich frag mich nun geht das in Standard C ja oder nein.
#5465403
Lesenswert?

g457 schrieb:
> Dass der Wert "zu groß" ist zwar ist formell richtig

Nach meiner Auffassung ist der Wert nicht "zu groß", sondern schlicht 
nicht erlaubt!

Wenn ich schreibe:
1
typedef enum {
2
Montag = 1,
3
Dienstag = 2,
4
Mittwoch = 4,
5
Donnerstag = 8,
6
Freitag = 16,
7
Samstag = 32,
8
Sonntag = 64
9
} Wochentag_t;
10

11
Wochentag_t Tag;
Und wenn ich dann Tag den Wert 5 zuweise, ist der Compiler in meinen 
Augen fehlerhaft, wenn er das erlaubt!
Gast #5465408
Lesenswert?

Thomas E. schrieb:
> Und wenn ich dann Tag den Wert 5 zuweise, ist der Compiler in meinen
> Augen fehlerhaft, wenn er das erlaubt!

Nein, das ist in C explizit erlaubt. Ein "enum i2s_mode_t" ist nur ein 
alias für "int", und "I2S_MODE_MASTER" ist nur eine int-Konstante (wobei 
C keine echten Konstanten hat, C++ schon).

Das hier:
1
typedef enum {
2
    I2S_MODE_MASTER = 1,
3
    I2S_MODE_SLAVE = 2,
4
    I2S_MODE_TX = 4,
5
    I2S_MODE_RX = 8,
6
    I2S_MODE_DAC_BUILT_IN = 16,       /*!< Output I2S data to built-in 
7
DAC, no matter the data format is 16bit or 32 bit, the DAC module will 
8
only take the 8bits from MSB*/
9
    I2S_MODE_ADC_BUILT_IN = 32,       /*!< Input I2S data from built-in 
10
ADC, each data can be 12-bit width at most*/
11
    I2S_MODE_PDM = 64,
12
} i2s_mode_t;
13

14

15
i2s_mode_t I2SMode;
16

17
int main () {
18
  I2SMode = I2S_MODE_MASTER | I2S_MODE_TX;
19
  return 0;
20
}
Kompiliert im GCC und im Clang auf maximaler Pingeligkeit problemlos. Es 
wird lediglich das überflüssige Komma am Ende des enums bemängelt.

Was für einen Compiler nutzt du denn? Kannst du Standard-Konformität 
einschalten? Ist das vielleicht eine MISRA-Warnung?

In C++11 kann man mit "enum class" solche Konvertierungen explizit 
verhindern; aber ich denke mal darum geht's hier nicht...
#5465415
Lesenswert?

Martin M. schrieb:
> Hallo und Danke für die Antworten.
> Thomas E.
> Du hast natürlich recht, deswegen habe ich ja auch den Link von Set of
> dazugefügt. Dann haben wir eine Menge und können mehrere Type
> Definitionen
> auswählen.
>
> Ich kann und will den Source nicht ändern das ist im ESP32 Arduino Core
> so definiert. Der Compiler
> meckert mit SP32_I2S.ino: 56:10: error: invalid conversion from 'int' to
> 'i2s_mode_t'
> Ich frag mich nun geht das in Standard C ja oder nein.

Das Problem ist: es handelt sich nicht um C-Code!
Arduino ist C++ und da werden Typen etwas weniger relaxed behandelt.
probier's mal so:
1
I2SMode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX );
#5465420
Lesenswert?

Thomas E. schrieb:
> Und wenn ich dann Tag den Wert 5 zuweise, ist der Compiler in meinen
> Augen fehlerhaft, wenn er das erlaubt!

In C sind Enums nicht wirklich typsicher, und ganz allgemein ist 
Typsicherheit nicht unbedingt das was C als Sprache auszeichnet. Böse 
Zungen  würden also sagen (insbesondere wenn sie einen 
Pascal-Hintergrund haben) nicht der Compiler ist fehlerhaft, die ganze 
Sprache ist ein einziger Fehler.
Gast #5465427
Lesenswert?

PS: Ich sehe gerade:

Martin M. schrieb:
> Ich kann und will den Source nicht ändern das ist im ESP32 Arduino Core
> so definiert. Der Compiler

Arduino nutzt C++, also geht es gar nicht um C. C++ ist hier etwas 
strenger. Schreibe einfach:
1
I2SMode = static_cast<i2s_mode_t> (I2S_MODE_MASTER | I2S_MODE_TX);

Da es sowieso C++ ist geht auch sowas:
1
#include <cstdint>
2

3
using i2s_mode_t = uint8_t;
4
static constexpr uint8_t I2S_MODE_MASTER = 1;
5
static constexpr uint8_t I2S_MODE_SLAVE = 2;
6
static constexpr uint8_t I2S_MODE_TX = 4;
7
static constexpr uint8_t I2S_MODE_RX = 8;
8
static constexpr uint8_t I2S_MODE_DAC_BUILT_IN = 16;
9
static constexpr uint8_t I2S_MODE_ADC_BUILT_IN = 32;
10
static constexpr uint8_t I2S_MODE_PDM = 64;
11

12
i2s_mode_t I2SMode;
13

14
int main () {
15
  I2SMode= I2S_MODE_MASTER | I2S_MODE_TX;
16
  return 0;
17
}

Oder noch besser:
1
#include <type_traits>
2
#include <cstdint>
3

4
enum class i2s_mode_t : uint8_t {
5
  NONE = 0,
6
    MASTER = 1,
7
    SLAVE = 2,
8
    TX = 4,
9
    RX = 8,
10
    DAC_BUILT_IN = 16,       /*!< Output I2S data to built-in 
11
DAC, no matter the data format is 16bit or 32 bit, the DAC module will 
12
only take the 8bits from MSB*/
13
    ADC_BUILT_IN = 32,       /*!< Input I2S data from built-in 
14
ADC, each data can be 12-bit width at most*/
15
    PDM = 64,
16
};
17

18
inline i2s_mode_t operator | (i2s_mode_t lhs, i2s_mode_t rhs) {
19
  return static_cast<i2s_mode_t> (static_cast<std::underlying_type_t<i2s_mode_t>> (lhs) | static_cast<std::underlying_type_t<i2s_mode_t>> (rhs));
20
}
21

22
inline i2s_mode_t operator & (i2s_mode_t lhs, i2s_mode_t rhs) {
23
  return static_cast<i2s_mode_t> (static_cast<std::underlying_type_t<i2s_mode_t>> (lhs) & static_cast<std::underlying_type_t<i2s_mode_t>> (rhs));
24
}
25

26
int main () {
27
  i2s_mode_t I2SMode= i2s_mode_t::MASTER | i2s_mode_t::TX;
28
  
29
  // ...
30
  
31
  if ((I2SMode & i2s_mode_t::TX) != i2s_mode_t::NONE) {
32
    // Transmitter einschalten ...
33
  }
34
}

Warum überhaupt die "typedef enum"-Konstruktion? Dies ist nur in C 
nötig, nicht in C++.
#5465448
Lesenswert?

Bernd K. schrieb:
> In C sind Enums nicht wirklich typsicher, und ganz allgemein ist

Ok, aber zumindest meckert der Compiler bei "Wochentag = 5"

Aber auch bei "Wochentag = 4" oder "Wochentag = Montag | Dienstag".

Nur durch Casten kann man die Fehlermeldung verhindern:

"Tag = (Wochentag_t)5;" oder "Tag = (Wochentag_t)(Montag | Dienstag);"
compiliert ohne Fehlermeldung.

Insofern habt ihr Recht, daß der Compiler nicht wirklich auf den 
erlaubten Wertebereich prüft, sondern nur auf formale Typendefinitionen.
Gast #5465628
Lesenswert?

Martin M. schrieb:

> In Pascal macht man Mehrzuweisungen mit einem Set of
> http://wiki.freepascal.org/Set

Das ist doch nur Eye-Candy. Letztlich machen diese ganzen 
Mengenoperatoren in Pascal nix anderes als die binären logischen 
Verknüpfungen in C. Der einzige Vorteil der Mengen ist, dass sie bis zu 
256Bit Breite implementiert sind. Wenn die Quelle der Raubkopie aber 
C-Code ist, spielt dieser Vorteil keine Rolle...

Und, fast noch wichtiger: man kann das dann auch in Pascal genauso 
ausdrücken wie in C, den Eye-Candy-Quatsch der Mengenoperatoren also 
einfach komplett außen vor lassen, denn Pascal kennt genau dieselben 
binär-logischen Operatoren wie C. Sie heißen halt nur anders...
#5465674
Lesenswert?

Dr. Sommer schrieb:
> Martin M. schrieb:
>> I2S_Mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX);
>
> Lieber einen named cast nutzen:I2S_Mode =
> static_cast<i2s_mode_t>(I2S_MODE_MASTER | I2S_MODE_TX);
> Dann ist klarer was passiert, und man kann weniger falsch machen.

In einem is2_mode_t diesen Wert zu speichern ist schon etwas, das falsch 
gemacht wurde. Wenn man da irgendwelche verODERungen der Enumeratoren 
speichern will, muss IS2Mode einfach vom Typ int statt i2s_mode_t sein. 
Dann braucht man auch keinen hässlichen Cast.
Gast #5465683
Lesenswert?

Rolf M. schrieb:
> In einem is2_mode_t diesen Wert zu speichern ist schon etwas, das falsch
> gemacht wurde. Wenn man da irgendwelche verODERungen der Enumeratoren
> speichern will, muss IS2Mode einfach vom Typ int statt i2s_mode_t sein.

Ansichtssache. Bitfelder in enums zu speichern ist erlaubt und absolut 
üblich. Ein Parameter "i2s_mode_t mode" ist deutlich sprechender als ein 
"int mode". Einen vorzeichenbehafteten Integer sollte man auch nicht 
dafür nehmen. Besser ist natürlich ein "enum class" mit entsprechend 
überladenen Operatoren...
#5465698
Lesenswert?

Dr. Sommer schrieb:
> Ansichtssache. Bitfelder in enums zu speichern ist erlaubt und absolut
> üblich.

Erlaubt ist es eben nicht (in C++). Deshalb muss man ja mit einem Cast 
den Compiler dazu zwingen, es trotzdem zu akzeptieren.
"I2S_MODE_MASTER | I2S_MODE_TX" ist vom Typ int, daher würde ich auch 
das Ergebnis in einem int speichern.

> Ein Parameter "i2s_mode_t mode" ist deutlich sprechender als ein
> "int mode".

Dafür könnte man sich ja zur Not noch einen Typedef basteln.

> Einen vorzeichenbehafteten Integer sollte man auch nicht dafür nehmen.

Ok, für Bitgefummel nimmt man besser unsigned. Da gebe ich dir recht.

> Besser ist natürlich ein "enum class" mit entsprechend überladenen
> Operatoren...

Ja.
Gast #5465702
Lesenswert?

Rolf M. schrieb:
> Erlaubt ist es eben nicht (in C++).

Sicher? Wo im Standard steht das?

Rolf M. schrieb:
> Warum nicht? Enumeratoren dürfen auch negativ sein.
Darf schon. Das Vorzeichenbit hat aber Sonderfunktionen, die nicht 
portabel sind. Wenn man z.B. anfängt damit zu shiften könnte es Probleme 
geben. Ob ein 16-bit-int den Wert -32768 darstellen kann ist auch nicht 
garantiert; ein 16-bit-"unsigned int" kann aber garantiert alle 16 
Bitmasken 1,2,4, ... 32768 ablegen.
#5465938
Lesenswert?

Dr. Sommer schrieb:
> Rolf M. schrieb:
>> Erlaubt ist es eben nicht (in C++).
>
> Sicher? Wo im Standard steht das?

Naja, grundsätzlich erlaubt ist es per expliziter Konvertierung, solange 
der Bereich der enum-Werte nicht verlassen wird. Schön finde ich es aber 
nicht. Und dass man dafür einen Cast braucht, zeigt auch für mich schon, 
dass es auch nicht so gedacht ist.
#5465972
Lesenswert?

c-hater schrieb:
> Und, fast noch wichtiger: man kann das dann auch in Pascal genauso
> ausdrücken wie in C, den Eye-Candy-Quatsch der Mengenoperatoren also
> einfach komplett außen vor lassen

Das ist kein Eye-Candy-Quatsch, das ist Typsicherheit und schön sauber 
hinzuschreiben und zu lesen. Wenn ein Pascal-Programm endlich kompiliert 
weil alle Typen stimmen und der Compiler nicht mehr meckert kannst Du 
davon ausgehen daß es auch funktioniert und das tut was Du willst. Zwar 
nicht immer aber ungefähr 4 bis 9 mal wahrscheinlicher als bei C.

> man kann das dann auch in Pascal genauso
> ausdrücken wie in C

Lol, niemand würde das ernsthaft wollen. Wozu auch?
Gast #5466229
Lesenswert?

Bernd K. schrieb:
> Das ist kein Eye-Candy-Quatsch, das ist Typsicherheit und schön sauber
> hinzuschreiben und zu lesen. Wenn ein Pascal-Programm endlich kompiliert
> weil alle Typen stimmen und der Compiler nicht mehr meckert kannst Du
> davon ausgehen daß es auch funktioniert und das tut was Du willst. Zwar
> nicht immer aber ungefähr 4 bis 9 mal wahrscheinlicher als bei C.

Bitte WAS? So einen Spruch habe ich das letzte Mal 1989 gehört, und zwar 
in einem Anfängerkurs den ich gegeben habe "Aber es compiliert, dann 
muss es doch funktionieren".

Wenn du das ernsthaft glaubst, dann ...
Gast #5466233
Lesenswert?

Jack schrieb:
> Bitte WAS? So einen Spruch habe ich das letzte Mal 1989 gehört, und zwar
> in einem Anfängerkurs den ich gegeben habe "Aber es compiliert, dann
> muss es doch funktionieren".

Dann besuch mal Kurse zu anderen höheren Programmiersprachen, wie Scala 
oder C++. Auch dort wird großer Wert auf Typsicherheit gelegt, da dies 
tatsächlich eine Menge Fehler vermeidet. Wenn nicht alles ein "void*" 
ist und der Compiler prüfen kann ob man kompatible Typen hat, muss man 
das nicht selbst tun. Natürlich bedeutet "kompiliert" hier nicht 
"funktioniert", aber die Wahrscheinlichkeit ist einfach größer.
#5466694
Lesenswert?

Jack schrieb:
> Bitte WAS?

Ja.

Es ist namlich nicht hauptsächlich der Fakt daß zufällig beim 
Kompilieren die Typen geprüft werden. Es ist vielmehr die Art wie Du in 
so einer Sprache programmierst, wie die Sprache Dir angewöhnt oder Dich 
geradezu zwingt Dir von Anfang an eingehende Gedanken und weitreichende 
Planung der Datentypen (Datenstrukturen) zu machen so daß Du oft schon 
bergeweise ausgeklügelte Typdeklarationen hast die genau zusammenpassen 
und den Gegenstand des Problems abbilden bevor Du auch nur eine 
einzige Zeile ausführbaren Code geschrieben hast. Und das bisschen 
Code das Du dann noch schreibst kann nicht mehr viel anders machen als 
die Puzzlestücke in der einzig erlaubten Weise in der sie zusammenpassen 
zu verwenden. Oft ist nach dem Ersinnen der Typen der Drops schon 
gelutscht und der Rest fügt sich ganz von selbst.

In C kann man einfach drauf los hacken und erst später nachdenken und 
das macht den Unterschied.

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