Arrayinitialisierungslisten



  • 1. Gibt es eine Möglichkeit, variable Initialisierungslisten zu erstellen?

    /*size ist ein statischer Wert, den ich aber hier nicht kenne.*/
    static const uint8_t array[size] =
    {
        /*Müssen alle im statischen Segment angelegt und direkt zugewiesen
        **werden - memset geht nicht, weil die Funktion reentrant sein muss.
        **static const darf nicht geändert werden.*/
    };
    

    2. Falls Ersteres nicht geht - sagt der C-Standard etwas über das Verhalten von übergroßen Initialisierungslisten?

    /*Einfach nur eine grosse Initialisierungsliste bauen.*/
    #define TRUE (1)
    #define TRUE_2 TRUE,TRUE,TRUE,TRUE,TRUE,TRUE,TRUE,TRUE
    #define TRUE_3 TRUE_2,TRUE_2,TRUE_2,TRUE_2,TRUE_2,TRUE_2,TRUE_2,TRUE_2
    #define TRUE_4 TRUE_3,TRUE_3,TRUE_3,TRUE_3,TRUE_3,TRUE_3,TRUE_3,TRUE_3
    #define TRUE_ENOUGH TRUE_4,TRUE_4,TRUE_4,TRUE_4,TRUE_4,TRUE_4,TRUE_4,TRUE_4
    
    static const uint8_t array[size] = {TRUE_ENOUGH};
    

    Unter dem GCC generiert das zwar Warnungen, kompiliert aber und erzeugt keine weiteren Felder im statischem Segment. Aber vielleicht würde ich hier nur UB ausnutzen.



  • Was soll konstant sein?
    Die Größe, die Elemente oder beide?
    Und was soll heißen "size ist ein statischer Wert", compilezeitkonstant oder laufzeitkonstant?

    Initialisierer von statischen Objekten (d.h. auch von allen globalen) müssen immer einen konstanten Ausdruck darstellen oder ein Stringliteral sein.
    Also scheiden variable Arrayelemente schon mal aus.
    Und variable Arraygrößen auch, denn dann wäre dein Array ein VLA, und die sind von Haus aus Scheiße:

    const int size = 34;
    int array[size] = {1,2,3};
    

    scheidet auch aus, da die Initialisierungsreihenfolge von globalen Objekten (ob nun konstant oder nicht) unspezifiziert ist, size kann bei der Array-Objektdefinition also einen anderen Wert als 34 haben, und das willst du wahrscheinlich nicht.

    static const uint8_t array[size] = {TRUE_ENOUGH};
    

    ist auch sinnlos, weil in Abhängigkeit von size die Elemente evtl. beschnitten werden, besser wäre da noch:

    static const uint8_t array[] = {TRUE_ENOUGH};
    

    Variabel und standardkonform kriegst du deine Fall nur durch Funktionen hin, die du aufrufst statt deine globalen Objekte direkt anzusprechen, also meinetwegen

    const int* f(unsigned x)
    {
      if(x==0)
      {
        static int a[] = {1,2,3};
        return a;
      }
      if(x==1)
      {
        static int a[100] = {1,2,3};
        return a;
      }
      ...
      {
        int *a = calloc(sizeof(int),x);
        a[0] = x+1;
        a[1] = x-1;
        ...
        return a;
      }
      ...
    }
    

    static im globalen Scope legt übrigens nicht die Speicherklasse fest sondern den Scope auf File/Modul-Scope.



  • Wutz schrieb:

    Die Größe, die Elemente oder beide?

    Beide.

    Wutz schrieb:

    Und was soll heißen "size ist ein statischer Wert", compilezeitkonstant oder laufzeitkonstant?

    Ersteres.

    static const uint8_t array[] = {TRUE_ENOUGH};
    

    Die Anzahl an Elementen steht immer zur Kompilierzeit bereit. Ich kenne ihn nur nicht unbedingt gerade. Er soll also nicht wirklich 8^4 mal TRUE im statischen Segment anlegen, derzeit sprechen wir noch von maximal 4 (soll aber halt zukunftssicher sein). Und eigentlich kann der Compiler auch für alle anderen Fälle, wo size < max_size ist, das gleiche Array verwenden. Ich ändere es ja schließlich nie. Ich muss nur eine Reihe von TRUE -Werten haben.

    Wutz schrieb:

    Variabel und standardkonform kriegst du deine Fall nur durch Funktionen hin, die du aufrufst statt deine globalen Objekte direkt anzusprechen

    Aber auch hier muss ich wieder wie ein Affe manuell Initialisierungslisten verwalten.

    Wutz schrieb:

    static im globalen Scope legt übrigens nicht die Speicherklasse fest sondern den Scope auf File/Modul-Scope.

    Habe das Gegenteil nicht impliziert. Wir befinden uns hier in inline-Funktionen, nicht im globalen Raum.
    Diese Arrays sollen nicht global verfügbar sein, sondern nur in den Funktionsaufrufen. Das Array gibt verschiedene Modi an (z.B. FALSE == keine Kopie, TRUE == Kopie). Wenn als Adresse NULL rauskommt, dann impliziert dies, dass alle Elemente FALSE sind, ansonsten hat man ein Array, in dem die verschiedenen Elemente angeben, was zu tun ist.

    Oft will man aber nur Deep Copy machen. Dann müssen(!) alle Elemente auf TRUE gesetzt sein. Der Programmierer soll kein eigenes TRUE -Array erstellen (womöglich noch auf dem Stack!), das ist redundant und fehleranfällig. Lieber einmal ordentlich geinlined.



  • @dachschaden:
    Ich verstehe dein Problem nicht ganz.

    Aber vielleicht gehts auch ein wenig anders. Könnte man nicht einfach ein größeres Feld fix mit einem Muster initialisieren und ein Programm schreiben welches die Exe öffnet, das Muster sucht und richtig initialisiert?

    static const uint8_t array[MAX_SIZE] = { 0x01, 0x02, 0x3, 0x04, .... };
    
    // Ein Byte Alignment einschalten
    
    typedef struct
    {
      uint32_t CRC;			// Prüfsumme über Feld
      uint8_t Structtype;	// Was für Daten sind im Struct gespeichert?
      uint8_t Version;		// Version des Struct Types  
      uint16_t PayloadSize;
      const uint8_t Payload[MAX_SIZE - 8];
    } tArrayField;
    
    // Ein Byte Alignment ausschalten
    
    void Bla()
    {
      const tArrayField* F = array;
    
      // Prüfe CRC
      if (F->Structtype == 1)
      {
         const tMyStruct* s = F->Payload; 
      }
      // ... 
    }
    

    Dumme Frage: Warum muss das Feld static const sein?



  • Man könnte das Problem auch mit compound literals erschlagen,
    entweder mit vorgegebener Max-Größe

    #include <stdbool.h>
    
    enum {MAXGROESSE=100};
    
    const int * const a = (int[MAXGROESSE]) {true,true,true,true};
    

    oder ohne: (was aber UB riskiert für den Zugriff auf undefinierten Speicher)

    #include <stdbool.h>
    
    const int * const a = (int[]) {true,true,true,true};
    


  • Bitte ein Bit schrieb:

    Ich verstehe dein Problem nicht ganz.

    Wo genau?

    Bitte ein Bit schrieb:

    Aber vielleicht gehts auch ein wenig anders. Könnte man nicht einfach ein größeres Feld fix mit einem Muster initialisieren und ein Programm schreiben welches die Exe öffnet, das Muster sucht und richtig initialisiert?

    Möglich - auf jeden Fall.
    Vernünftig - eher nicht. Stattdessen habe ich dann nur noch mehr Code, den ich verwalten muss (das Programm, welches den Austausch vornimmt).

    Bitte ein Bit schrieb:

    Dumme Frage: Warum muss das Feld static const sein?

    static - es macht einfach absolut keinen Sinn, bei einem Funktionsaufruf ein Array, welches sich nie ändert, immer wieder neu auf dem Stack zu erzeugen. Beim Inlinen wird der Effekt über mehrere Funktionsaufrufe eventuell abgeschwächt (nur eine Erzeugung im Caller statt Erzeugungen - Plural - im Callee), aber es ist trotzdem einfach komplett unnötig.

    const - leitet sich aus static ab. Wenn wir ein statisches Feld in der Funktion haben, müssen direkt mit der Initialisierung alle Werte bereitstehen. Ein verspätetes Zuweisen kann und wird der Compiler in Zuweisungscode umwandeln, und nicht direkt damit das statische Segment füllen:

    movq $0x1,0x200b3b(%rip) # 601040 <a.2239>
    movq $0x2,0x200b38(%rip) # 601048 <a.2239+0x8>
    mov  0x200b31(%rip),%rdx # 601048 <a.2239+0x8>
    mov  0x200b22(%rip),%rax # 601040 <a.2239>
    

    Und damit verliert die Funktion ihre Eintrittsinvarianz. Ich müsste also Locks bauen, wieder für etwas, was eigentlich ein Standardfeature der Sprache sein sollte.

    Wenn ich dann die Lockidee wegwerfe, bleibt mir nichts anderes übrig, als das Feld direkt korrekt zu initialisieren. Und dann kann es direkt const sein. Ich ändere es ja nie.

    Wutz schrieb:

    #include <stdbool.h>
     
    const int * const a = (int[]) {true,true,true,true};
    

    Die Idee gefällt mir. Ein Problem ist wohl, dass die Arraygrößen wegfallen, wodurch ich dann nicht mehr einfach prüfen kann, ob zu wenig Elemente in der Liste gesetzt wurden.

    Ich habe den originalen Code die Nacht noch mal geändert - in der Header-Datei habe ich jetzt die Deklaration eines globalen Feldes:

    extern const int*const array_true;
    

    In dem Modulcode definiere ich dann:

    const int*const array_true = (int[])
    {
        TRUE,TRUE,TRUE,TRUE,TRUE,TRUE,TRUE,TRUE,
        TRUE,TRUE,TRUE,TRUE,TRUE,TRUE,TRUE,TRUE
    }
    

    Auf diese Weise kann ich noch bei deaktivierter LTO (falls diese sowas überhaupt optimieren kann) den Code dazu zwingen, immer das gleiche TRUE-Array zu nehmen. Das Problem mit der Maximalgrenze habe ich ja so oder so, nur vermeide ich zusätzlich garantiert unnötige Kopien (die beim Inline-Code zwar nicht mehr auf dem Stack, aber im statischen Segment der Objektdatei landen würden).

    So kompiliert es ohne Warnung. Ich glaube, so lasse ich es auch, es sei denn, jemand findet darin noch Fehler. Ein paar Testläufe jedenfalls zeigen keine Probleme damit.



  • es macht einfach absolut keinen Sinn, bei einem Funktionsaufruf ein Array, welches sich nie ändert, immer wieder neu auf dem Stack zu erzeugen

    Ein Array wird als Funktionsparameter immer als Zeiger umgewandelt und nur der Zeiger geht dann auf den Stack.

    Ein Problem ist wohl, dass die Arraygrößen wegfallen, wodurch ich dann nicht mehr einfach prüfen kann, ob zu wenig Elemente in der Liste gesetzt wurden.

    Deshalb habe ich die erste Variante angegeben.



  • Wutz schrieb:

    Ein Array wird als Funktionsparameter immer als Zeiger umgewandelt

    Und wo habe ich gesagt, dass das Array ein Funktionsparameter ist?
    Wenn es das wäre, dann müsste der Nutzer ja sein eigenes Array erstellen. Und genau das will ich ja vermeiden.



  • static - es macht einfach absolut keinen Sinn, bei einem Funktionsaufruf ein Array, welches sich nie ändert, immer wieder neu auf dem Stack zu erzeugen. Beim Inlinen wird der Effekt über mehrere Funktionsaufrufe eventuell abgeschwächt (nur eine Erzeugung im Caller statt Erzeugungen - Plural - im Callee), aber es ist trotzdem einfach komplett unnötig.

    Ähh ja. Und ein void Test(const uint8_t× Data) funktioniert da nicht? 😕

    Aber ich glaube ich weis worauf du hinaus willst. Du wirst mich vermutlich steinigen wenn ich empfehle: inline const uint8_t× GetData().

    Und damit verliert die Funktion ihre Eintrittsinvarianz. Ich müsste also Locks bauen, wieder für etwas, was eigentlich ein Standardfeature der Sprache sein sollte.

    Reden wir hier von Multithreading oder Multitasking? Evt. auf einem Mikrocontroller?

    Ab C11 existieren rudimentäre Threading Strukturen (Mutex, Condition Vatiable,...)

    ---

    Und Makro Tricks funktionieren da nicht? Weist du zur Compilezeit wie groß dein Feld ist? Ich habe schon gesehen wie man mittels eines #include aus Textdateien zur Compilezeit ein Enum generierte und so an die Anzahl der Elemente kam und eine dazugehörige Variable.



  • Bitte ein Bit schrieb:

    Ähh ja. Und ein void Test(const uint8_t× Data) funktioniert da nicht? 😕

    Wenn du damit meinst, dass ich ein solches Array vom Caller bereitstellen lassen soll - genau das ist der Punkt. Genau das soll der Caller nicht brauchen.

    dachschaden schrieb:

    Dann müssen(!) alle Elemente auf TRUE gesetzt sein. Der Programmierer soll kein eigenes TRUE-Array erstellen (womöglich noch auf dem Stack!), das ist redundant und fehleranfällig. Lieber einmal ordentlich geinlined.

    Bitte ein Bit schrieb:

    Aber ich glaube ich weis worauf du hinaus willst. Du wirst mich vermutlich steinigen wenn ich empfehle: inline const uint8_t× GetData().

    Wieso? Der Effekt über die externe globale Variable, die ich jetzt habe, sorgt zusätzlich dafür, dass ich garantiert keine doppelten Felder in Objektdateien rumliegen habe. Ich habe ein Array, statisch und konstant, für alle Fälle, solange eine bestimmte Größe nicht überschritten wird (und auch hier kann ich noch eine Prüfung einfügen - ein bisschen redundant immer noch, aber soweit ich sehe besser als die Alternativen).

    Mit einer solchen Funktion würde ich aber in jeder Übersetzungseinheit ein solches Array erzeugen. Weil die Funktion inlined wird, erhält jede TU ihre eigene Kopie, mit einem eigenen statischen Speicherbereich. Das ist auch reichlich bala-bala.

    Bitte ein Bit schrieb:

    Reden wir hier von Multithreading oder Multitasking?

    Ich rede von Multithreading.
    Das sind sehr oft aufgerufene Funktionen, sozusagen die wohldefinierte Schnittstelle, über die Daten in Speicherstrukturen eingefügt werden. Wegen so einer Kinderkacke einen Lock für jede Instanz einer Arraygröße zu erstellen sollte ebenso komplett unnötig sein wie die Erzeugung eines Arrays bei jedem Call.

    Bitte ein Bit schrieb:

    Evt. auf einem Mikrocontroller?

    Auch. Es handelt sich um ein Modul einer größeren Bibliothek. Und verschiedene Teile davon laufen auf verschiedenen Systemen.

    Bitte ein Bit schrieb:

    Ab C11 existieren rudimentäre Threading Strukturen (Mutex, Condition Vatiable,...)

    Das Problem ist nicht, dass ich diese nicht verwenden kann.
    Das Problem ist, dass ich diese für erwähnte Kinderkacke nicht verwenden will. Um ein Feld, welches nicht doppelt angelegt werden soll, ordentlich zu initialisieren, soll ich ein Lock verwenden, weil die Sprache mir nicht erlaubt, statisch einen Nicht-Null-Standardwert zuzuweisen? Da gackern ja die Hühner!

    (Außerdem bin ich mir nicht sicher, ob Visual Studio C11 soweit unterstützt. Der GCC hat eine Extension, um so einen Standardwert zuweisen zu können, aber für Windows bringt mir das natürlich nichts.)

    Bitte ein Bit schrieb:

    Und Makro Tricks funktionieren da nicht?

    Der Grund, warum ich überhaupt erstmal nicht weiß, wie viele Elemente ich drin habe, ist weil ich mich streng genommen bereits in einem Makro befinde. Die Anzahl der Elemente ist ganz leicht über ein sizeof auf ein Feld in einer angegebenen Struktur zu ermitteln, aber wenn ich den Code schreibe, weiß ich nicht, wie viele Elemente da eigentlich drin sind. Die Größe ist zur Kompilierzeit bekannt, aber nicht zur Programmierzeit.

    Wenn du Maktotricks kennst, über die man Initialisierungslisten generieren kann, sag' mir Bescheid. Bisher habe ich noch nichts gefunden - aber seit ich die obige Version eingefügt habe, habe ich zugegebenermaßen auch nicht mehr nachgeschaut.

    Bitte ein Bit schrieb:

    Weist du zur Compilezeit wie groß dein Feld ist? Ich habe schon gesehen wie man mittels eines #include aus Textdateien zur Compilezeit ein Enum generierte und so an die Anzahl der Elemente kam und eine dazugehörige Variable.

    Was bringt mir das, wenn ich doch nur wieder an der Initialisierungsliste scheitere? Das Array generiere ich in der passenden Größe ohne Probleme, die Zuweisung der Standardwerte ist's, wo's bricht.

    Aber wie gesagt, derzeit arbeite ich lieber mit der externen Variabel. Ich werde wahrscheinlich noch ein define einfügen, über das man die maximale Anzahl an Elementen steuern kann, dann habe ich noch eine Chance, auf Reads außerhalb meines Arrays zu reagieren.


Anmelden zum Antworten