Forum: Mikrocontroller und Digitale Elektronik Verhalten von Array mit 0 Feldern


von André W. (sefiroth)


Lesenswert?

Hallo zusammen,

ich habe eine eher allgemeine Frage zu Arrays in C.

Ich schreibe gerade ein Programm, dass ein Struktur-Array mittels define 
anlegt:
1
#define FELDER 10
2
3
typedef struct
4
{
5
   uint8_t u8_Option1;
6
   uint8_t u8_Option2;
7
  
8
} meine_struktur_t;
9
10
meine_strukur_t s_struktur[FELDER];

Jetzt ist es im Programm realistisch möglich, dass FELDER mit 0 
definiert wird (im Beispiel wäre es blöd, aber ist halt nur ein 
Beispiel). Das bemängelt C beim Compilieren auch nicht. Ich frage mich 
wie das im Speicher aussieht ;-) Wird da überhaupt Speicherplatz 
reserviert? Ein Array mit 0 Feldern kann ja gar nicht verwendet 
werden...

Wie würdet Ihr den Fall abfangen? Ich tendiere jetzt dazu, im Falle von 
FELDER == 0 mindestens eine Struktur anzulegen:
1
#define FELDER 10
2
3
typedef struct
4
{
5
   uint8_t u8_Option1;
6
   uint8_t u8_Option2;
7
  
8
} meine_struktur_t;
9
10
#if (FELDER > 0)
11
meine_strukur_t s_struktur[FELDER];
12
#else
13
meine_strukur_t s_struktur[1];
14
#endif

Im Hauptprogramm wird vor Benutzung des Arrays natürlich auch geprüft, 
ob FELDER > 0 ist. Geschrieben/Gelesen wird nur, wenn es überhaupt 
FELDER gibt. Aber nichtsdestotrotz macht mich ein Null-Felder-Array 
nervös ;-)

Gruß,
André

von Chefkoch (Gast)


Lesenswert?

Das "Nullte" Element eines Arrays ist ein Pointer auf den Begin des 
Arrays. Das bedeutet, die Variable die du für das Arrays anlegst ist 
letztlich ein Pointer eines bestimmten Typen, in deinem fall eine 
Struct.
Das heißt ein Array besteht aus einem Zeiger auf das Nullte Argument und 
den tatsächlich reservierten Speicherstellen.

Gibst du also Null Speicherstellen an, hast du nur noch einen Pointer 
vom angegebenen Typ.

Das kann man z.B. nutzten, wenn man zur Compilezeit nur weiß, DASS es 
ein Array geben soll, aber noch nicht mit wie viel Speichertiefe. Dann 
macht man einen Pointer vom entsprechenden Typ, dem dann über malloc, 
new, was_auch_immer speicherstellen zugewiesen werden können. Danach 
lässt sich der zeiger mit einem Arrayindex verwenden.

von (prx) A. K. (prx)


Lesenswert?

André Wippich schrieb:
> Ein Array mit 0 Feldern kann ja gar nicht verwendet werden...

Es ist per Sprachdefinition nicht zulässig.

von DirkB (Gast)


Lesenswert?

Ein Array ist kein Pointer. (Auch ein Array der Größe 0 nicht)
Die Adresse von einem Array kann man nach der Definition nicht mehr 
ändern.

ISO-C verbietet Arrays der Größe 0.
gcc meckert das auch an bei -pedantic

sizeof() liefert dafür auch 0

Da offensichtlich ein Fehler vorliegt:
1
#if (FELDER == 0) 
2
#error "FELDER ist 0"
3
#endif

von André W. (sefiroth)


Lesenswert?

DirkB schrieb:
> Da offensichtlich ein Fehler vorliegt:
>
1
#if (FELDER == 0)
2
> #error "FELDER ist 0"
3
> #endif

Im Beispiel ist es ein klarer Fehler, im realen Programm aber eine 
zulässige Definition. Wäre schön, wenn ich es so leicht abfangen könnte 
;-)

Klingt so, als wäre mein Lösungsansatz (Array dann sicherheitshalber mit 
1 anlegen) schon der vielversprechendste.

von Udo S. (urschmitt)


Lesenswert?

André Wippich schrieb:
> im realen Programm aber eine zulässige Definition.

Wie das?
Dann musst du per #if #endif alles ausklammern, denn das Array benutzen 
kannst du nicht wenn es die Größe 0 hat.
Insofern würde ich statt "zulässige Definition" eher "versteckter 
Fehler" sagen.

von Chefkoch (Gast)


Lesenswert?

War dein Code nur ein Fallbeispiel und du willst letztlich dynamisch 
allokieren?

Wenn es statisch mittels #define bleiben soll, kannst du auch das 
"define´te" FELDER nur einmal am Anfang mit Präprozessordirektiven 
abfragen und behandeln. Das hält den Code besser lesbar.

von zu vielen #ifdef´s im laufenden Code kriegt man Augenkrebs...

von André W. (sefiroth)


Lesenswert?

Chefkoch schrieb:
> War dein Code nur ein Fallbeispiel und du willst letztlich dynamisch
> allokieren?
>
> Wenn es statisch mittels #define bleiben soll, kannst du auch das
> "define´te" FELDER nur einmal am Anfang mit Präprozessordirektiven
> abfragen und behandeln. Das hält den Code besser lesbar.
>
> von zu vielen #ifdef´s im laufenden Code kriegt man Augenkrebs...

Der Code ist wirklich nur ein Minimal-Fallbeispiel. Ich will nichts 
dynamisch allokieren, sondern habe ein recht großes bestehendes 
Programm, dass durch Erweiterungen auch mit dem neuen Sonderfall 0 
FELDER klar kommen muss. In dem Fall wird das Array vom Programm nicht 
verwendet, da überall vorher gepürft (werden) wird, ob FELDER > 0 ist.

Die Frage war nur, wie lege ich das ungenutzte Array an, so dass es 
keinen Unsinn im Speicher anstellt ;-)

Auf haufenweise #ifdefs möchte ich wirklich verzichten. Wenn ich das 
Array einfach mit mindestens einem Feld anlegen lasse und dann nicht 
verwende bin ich ja an allen Fronten auf der sicheren Seite...

von (prx) A. K. (prx)


Lesenswert?

#define NONZERO(n) ((n)?(n):1)
meine_strukur_t s_struktur[NONZERO(FELDER)];

von Karl H. (kbuchegg)


Lesenswert?

André Wippich schrieb:

> FELDER klar kommen muss. In dem Fall wird das Array vom Programm nicht
> verwendet

d.h. du schaltest effektiv Funktionalität ab.

Genau so wird das üblicherweise gehandhabt, dass man das ganze so 
ansieht, ob man eine bestimmte Funktionalität auch haben will und die 
entsprechenden Abfragen (ob mit #if oder anders) in genau diese Richtung 
formuliert: Funktionalität ist eingeschaltet.

> da überall vorher gepürft (werden) wird, ob FELDER > 0 ist.

denn dabei wird es nicht bleiben. Oft gibt es dann auch noch andere 
Variablen die überflüssig geworden sind, wenn nicht sogar ganze 
Funktionen wegfallen, wenn besagte Funktionalität abgeschaltet wird.

um in einer UART Lib den Ringbuffer loszuwerden, setzt man nicht die 
Größe des Ringbuffers auf 0, sondern man hat ein
1
#define USE_RINGBUFFER
entweder hängt man dann die Inklusion des Ringbuffers an die Existenz 
des Makros (in dem Fall wird dann das Makro auskommentiert um die 
Funktionalität loszuwerden) oder an bestimmte Werte (meist 0 oder 1) mit 
entsprechenden #if

Wenn es aber einen Ringbuffer gibt, angezeigt durch das Makro 
USE_RINGBUFFER, dann hat seine Größe auch nicht 0 zu sein.

von André W. (sefiroth)


Lesenswert?

Karl Heinz schrieb:
> um in einer UART Lib den Ringbuffer loszuwerden, setzt man nicht die
> Größe des Ringbuffers auf 0, sondern man hat ein
>
1
> #define USE_RINGBUFFER
2
>
> ...
> Wenn es aber einen Ringbuffer gibt, angezeigt durch das Makro
> USE_RINGBUFFER, dann hat seine Größe auch nicht 0 zu sein.

Uff, Du hast natürlich Recht. Manchmal sieht man den Wald vor lauter 
Bäumen nicht ;-)

von Fritz G. (fritzg)


Lesenswert?

Mache einfach
1
#if (FELDER > 0)
2
meine_strukur_t s_struktur[FELDER];
3
#endif

Wenn du das dann übersetzt (mit FELDER==0) bekommst du überall 
Compilerfehler, wo du auf die Struktur zugreifst. Damit weisst du 
automatisch wo das #if noch hin muss.
Es darf ja dann keinen Code mehr geben, der deine Struktur verwendet.

von André W. (sefiroth)


Lesenswert?

Fritz Ganter schrieb:
> Mache einfach
>
1
> #if (FELDER > 0)
2
> meine_strukur_t s_struktur[FELDER];
3
> #endif
4
>
>
> Wenn du das dann übersetzt (mit FELDER==0) bekommst du überall
> Compilerfehler, wo du auf die Struktur zugreifst. Damit weisst du
> automatisch wo das #if noch hin muss.
> Es darf ja dann keinen Code mehr geben, der deine Struktur verwendet.

Wäre machbar, aber ich möchte "#if #endif" im Programmcode lieber 
vermeiden/minimal halten. Ich finde das ab einer bestimmten Komplexität 
zu schlecht lesbar.

von Jay (Gast)


Lesenswert?

André Wippich schrieb:
> Wäre machbar, aber ich möchte "#if #endif" im Programmcode lieber
> vermeiden/minimal halten. Ich finde das ab einer bestimmten Komplexität
> zu schlecht lesbar.

Der Preis den du dafür bezahlst (nichts ist umsonst) ist, dass du oder 
jemand anderes eventuell irgendwo ein Test
1
if(FELDER > 0) {
2
    ...
3
}
vergisst und es dann zur Laufzeit einen Fehler gibt.

Daher sehe ich nicht, warum statt dessen an den selben Stellen ein
1
#if(FELDER > 0)
2
...
3
#endif
schlechter lesbar sein soll. Wie du selber schreibst, testen musst du 
sowieso.

Was du in beiden Fällen machen kannst ist den Code umzustrukturieren, so 
dass du nur wenige Stellen hast an denen auf das Array zugegriffen wird. 
Am besten ein paar wenige Funktionen, isoliert in einer einzigen Datei.

Hat man erst mal alle möglichen Operationen auf das Array in ein paar 
Funktionen gegossen, kann man das Ganze noch steigern. Man implementiert 
zwei Versionen jeder Operation, für FELDR == 0 und für FELDER > 0. Dann 
wählt man mit einem einzigen #if den jeweils richtigen Satz von 
Funktionen aus. Das lohnt sich nur, wenn der Aufwand zwei Versionen zu 
schreiben vertret bar ist. Zum Beispiel, wenn die Implementierungen sehr 
unterschiedlich sind, man im anderen Fall also sehr viele #if in eine 
Funktion einstreuen müsste.

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.