Dynamisches Array und vorgefertigte Daten

Lesezeit: 11 Minuten

Benutzer-Avatar
Armin Rigo

In C versuche ich folgendes zu tun:

typedef struct {
    int length;
    int items[];     /* 1 */
} wchararray_t;

typedef struct {
    long hash;
    wchararray_t chars;   /* 2 */
} string_t;

static string_t s1 = {
    617862378,
    { 5, { 'H', 'e', 'l', 'l', 'o' } }  /* 3 */
};

In vollen Worten, ich hätte gerne einen Typ string_t das endet in einem anderen Typ wchararray_t das selbst eine dynamische Größe hat – seine Größe wird darin gespeichert length. Außerdem möchte ich hier auch einen vorgefertigten bestimmten String als statische Daten schreiben s1 Länge 5.

Der obige Code setzt C99-Unterstützung voraus /* 1 */. Die Einbeziehung der Unterkonstruktion in die größere Struktur bei /* 2 */ wird, soweit ich weiß, nicht einmal vom C99-Standard unterstützt – aber GCC akzeptiert es. Allerdings bei /* 3 */ GCC gibt auf:

error: initialization of flexible array member in a nested context

Als Problemumgehung ist der obige ideale Code bisher als folgender Hack geschrieben, der “irgendwie funktioniert”:

typedef struct { int length; int items[1]; } wchararray_t;
typedef struct { long hash; wchararray_t chars; } string_t;

typedef struct { int length; int items[5]; } wchararray_len5_t;
typedef struct { long hash; wchararray_len5_t chars; } string_len5_t;

static union { string_len5_t a; string_t b; } s1 = {
    617862378,
    { 5, { 'H', 'e', 'l', 'l', 'o' } }
};

… und wir würden “s1.b” als vorgefertigten string_t verwenden (und niemals auf “s1.a” verweisen, das hier nur für die statische Deklaration von s1 dient). Es bricht jedoch im neuesten GCC 4.8 zusammen, das Teile unseres Codes wegoptimiert, da – offensichtlich – jede Schleife über die items von a wchararray_t kann nur einmal iterieren, da es sich um ein Array der Länge 1 handelt.

Dieses spezielle Problem wird behoben, indem gcc die Option gegeben wird -fno-aggressive-loop-optimizations. Es kann wahrscheinlich auch behoben werden, indem die Länge nicht angegeben wird wchararray_t‘s items[] Array, was es zu einem dynamischen Array “nur weil” macht. Diese Art, Code zu schreiben, ist jedoch ein solcher Hack, dass ich eine völlig andere Herangehensweise an das Problem vorziehen würde …

(Beachten Sie, dass es sich um generierten C-Code handelt, der von PyPy erzeugt wird, im Gegensatz zu handgeschriebenem Code; jede Änderung ist in Ordnung, auch wenn es erforderlich ist, die Art und Weise zu ändern, wie wir überall auf die Daten zugreifen, solange die “gültigen” C-Optimierungen dies nicht sind verhindert.)

BEARBEITEN: ersetzt “char[]” mit “int[]”, die die doppelte Anführungszeichen-Syntax nicht akzeptiert "hello". Dies liegt daran, dass ich nach einer Lösung für jeden Array-Typ suche.

NICHT GELÖST: danke an alle für eure vorschläge. Es scheint keinen sauberen Weg zu geben, also habe ich die hackige Lösung implementiert: die Typen k+1 mal zu deklarieren, einmal mit einem flexiblen Array “int items[];” und das k andere Male mit “int items[N];” für die verschiedenen Werte von N, die benötigt werden. Dies erfordert einige zusätzliche Hacks: z. B. keine Verwendung flexibler Arrays für MSVC (sie funktionieren dort anders; ich habe nicht untersucht, ob genau dieselbe Syntax funktionieren würde); und GCC folgt was C99 sagt und nicht glücklich mit Strukturen ist, die enthalten würden int items[]; als einziges Feld. Es ist jedoch glücklich, wenn wir ein Dummy-Feld hinzufügen char _dummy[0];… was meines Wissens nicht unbedingt C99 ist …

  • Meinst du für items[] ein Array von sein int?

    – David R. Tribble

    15. April 2013 um 20:51 Uhr

  • Ja. Sie können ersetzen 'H', 'e', 'l', 'l', 'o' mit einer Liste von 5 normalen Ints, wenn Sie es vorziehen.

    – Armin Rigo

    15. April 2013 um 21:33 Uhr

Benutzer-Avatar
Alok Singhal

Es ist hackish, aber könnte das funktionieren?

#include <stdio.h>

typedef struct {
    int length;
    int items[];     /* 1 */
} wchararray_t;

typedef struct {
    long hash;
    wchararray_t chars;   /* 2 */
    int dummy[]; /* hack here */
} string_t;

static string_t s1 = {
    617862378, { 5 },
    { 'H', 'e', 'l', 'l', 'o' }  /* 3: changed assignment */
};

int main(void)
{
    int i;
    for (i=0; i < 5; ++i) {
        putchar(s1.chars.items[i]);
    }
    putchar('\n');
    return 0;
}

GCC warnt mich:

xx.c:10:22: warning: invalid use of structure with flexible array member [-pedantic]
xx.c:16:9: warning: initialization of a flexible array member [-pedantic]
xx.c:16:9: warning: (near initialization for ‘s1.dummy’) [-pedantic]

Aber es scheint zu funktionieren.

Bezug

Bearbeiten: Wie wäre es mit dem Hinzufügen eines “Padding-Mitglieds”, das sicherstellt items[] ist immer richtig ausgerichtet?

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>

/* change to the strictest alignment type */
typedef long aligner;

typedef struct {
    long stuff;   /* to show misalignment on 64-bit */
    int length;
    aligner padding;
    int items[];
} chararray_t;

typedef struct {
    long hash;
    chararray_t chars;
    int dummy[];
} string_t;

static string_t b1 = {
    617862378,
    { 42, 5 },
    {-1, -2, -3, -4, -5}
};

int main(void)
{
    int i;

    printf("sizeof chararray_t: %zu\n", sizeof(chararray_t));
    printf("offsetof items: %zu\n", offsetof(chararray_t, items));

    printf("sizeof string_t: %zu\n", sizeof(string_t));
    printf("offsetof dummy: %zu\n", offsetof(string_t, dummy));

    for (i=0; i < 5; ++i) {
        printf("%d ", b1.chars.items[i]);
    }
    putchar('\n');
    for (i=0; i < 5; ++i) {
        printf("%d ", b1.dummy[i]);
    }
    putchar('\n');
    return 0;
}

Wenn ich das obige ausführe, scheine ich die richtige Antwort zu bekommen:

sizeof chararray_t: 24
offsetof items: 24
sizeof string_t: 32
offsetof dummy: 32
-1 -2 -3 -4 -5 
-1 -2 -3 -4 -5 

  • Nein, ich habe tatsächlich darüber nachgedacht, aber es funktioniert nicht bei Ausrichtungsproblemen. Sie wissen nicht, dass das Dummy-Array direkt nach der “int-Länge” beginnt. Dies wäre nicht der Fall, wenn wchararray_t auf 64-Bit-Rechnern auch ein Feld vom Typ „long“ enthalten würde: sizeof(wchararray_t)==16 statt 12 aus Ausrichtungsgründen, aber Sie möchten, dass das Array bereits nach 12 Bytes beginnt (wie es wäre natürlich wenn es als deklariert wurde int items[actual_size]).

    – Armin Rigo

    15. April 2013 um 19:00 Uhr


  • @ArminRigo: das Array items würde aufgrund von Ausrichtungsproblemen nicht nach 12 Bytes beginnen, ja. Aber wenn doch, dummy und items würde nicht “zusammenpassen”, also tun Sie es nicht wollen die Polsterung, um das Array zu erstellen items Beginnen Sie in diesem Fall trotzdem bei Byte 16?

    – Alok Singhal

    15. April 2013 um 19:53 Uhr

  • Nein, aus dem Originalcode, wenn Sie den Typ machen struct { int length; int items[5]; } dann die 5 * 4 bytes von items Beginnen Sie nach den 4 Bytes von length. Wenn Sie diese Struktur dann in string_t einfügen items Beginnen Sie bei Byte 12.

    – Armin Rigo

    15. April 2013 um 20:13 Uhr

  • @ArminRigo, könnten Sie in diesem Fall Code generieren, um eine Auffüllung hinzuzufügen char Anordnung dazwischen? Etwas wie typedef struct { long stuff; int length; char padding[4]; int items[]; } chararray_t;. Natürlich die Größe von padding (und ob es vorhanden ist oder nicht) hängt von den Ausrichtungsanforderungen ab.

    – Alok Singhal

    16. April 2013 um 0:07 Uhr

  • Die Größe von padding hängt davon ab, in welche andere Struktur es eingebettet ist. Es kann in verschiedene Arten von Strukturen eingebunden werden. Ganz zu schweigen davon, dass es schwierig ist, die genauen Ausrichtungsdetails im Voraus zu kennen, die ein bestimmter C-Compiler auf einer bestimmten Plattform erzeugen wird.

    – Armin Rigo

    16. April 2013 um 8:24 Uhr

Beantwortung meiner eigenen Frage, um sie aufzuschreiben. Ein weiterer Hack wäre, auf Aloks Vorschlag aufzubauen, was gelegentlich zu einer falschen Ausrichtung führen kann – und dann die Ausrichtung durch Init-Time-Code zu korrigieren. Dies setzt voraus, dass die große Mehrheit solcher Typen, die in einem Programm verwendet werden, korrekt ausgerichtet sind. Code:

typedef struct {
    long stuff;   /* to show misalignment on 64-bit */
    int length;
    int items[];
} chararray_t;

typedef struct {
    long hash;
    chararray_t chars;
    int dummy[];
} string_t;


static string_t b1 = {
    617862378,
    { 42, 5 },
    {-1, -2, -3, -4, -5}
};
/* same with b2 .. b6 */

void fixme(void) {
    /* often compares as equal, and the whole function is removed */
    if (offsetof(string_t, dummy) !=
            offsetof(string_t, chars) + offsetof(chararray_t, items)) {
        static string_t *p_array[] = { &b1, &b2, &b3, &b4, &b5, &b6 };
        string_t *p;
        int i;
        for (i=0; i<6; i++) {
            p = p_array[i];
            memmove(p->chars.items, p->dummy, p->chars.length * sizeof(int));
        }
    }
}

  • Hat diese Methode in den Fällen, in denen Sie diesen Code ausführen müssen, einen wirklichen Vorteil gegenüber der Verwendung der Laufzeitinitialisierung?

    – Alex Gaynor

    16. April 2013 um 4:10 Uhr

  • Wenn der Hauptpunkt nur die statische Initialisierung ist, können Sie sie nicht durch Generieren von ersetzen unsigned char static_data[]={...}; und dann verwenden (*((string_t *)(static_data+12345))) im generierten Code statt s1?

    – 6502

    16. April 2013 um 6:05 Uhr

  • Wir können nicht nur an verwenden unsigned char da einige Felder mit Zeigern auf andere statische Daten initialisiert werden müssen; es ist also im Grunde gleichbedeutend mit der Deklaration der statischen Variablen als string_len5_t und werfen sie zu string_t. Aber das erzeugt GCC-Warnungen mit -Wstrict-aliasingauch wenn es wahrscheinlich kein schlechter Code ist, wenn wir den Cast systematisch durchführen, um auf die Strukturen zuzugreifen.

    – Armin Rigo

    16. April 2013 um 8:30 Uhr

  • @AlexGaynor: Ja, der erste Vorteil ist, dass die Funktionen die meiste Zeit leer sind, da das Laufzeit-Padding meistens nicht benötigt wird. Aber genau genommen weiß nur der C-Compiler, wann es gebraucht wird oder nicht; wenn es nicht benötigt wird, ist die statische Deklaration bereits korrekt. Der andere Vorteil besteht darin, dass zum Initialisieren von Daten viel weniger Platz benötigt wird als zum Schreiben von Code, der das Array Element für Element füllt.

    – Armin Rigo

    16. April 2013 um 8:45 Uhr

  • @ArminRigo Das, was mir beim Ausprobieren des unsigned char-Arrays am nächsten kam, war die Verwendung von Offsets anstelle von Zeigern und die Verwendung eines Makros OBJ zum Beispiel &OBJ(Node,n->child_offset[i]) Anstatt von n->child[i] (Dies galt für einen Baumknoten mit einer variablen Anzahl von Kindern pro Knoten, die als dynamisches Array am Ende des Knotenobjekts gespeichert sind). Der generierte Maschinencode enthielt jedoch ein Extra addq $data, %rdi um den Zeiger aus dem Offset neu zu erstellen.

    – 6502

    16. April 2013 um 13:26 Uhr


Benutzer-Avatar
Binayaka Chakraborty

#include <stdio.h>
typedef struct {
    int length;
    char items[];     /* 1 */
} chararray_t;

typedef struct {
    long hash;
    chararray_t chars;   /* 2 */
} string_t;

/*static string_t s1 = {
    617862378,
    { 5, { 'H', 'e', 'l', 'l', 'o' } }  // 3
};*/

static string_t s1 =
{
    617862378,
    {6,"Hello"} /* 3 */
};

int main()
{
    printf("%d %d %s\n",s1.hash,s1.chars.length,s1.chars.items);
    return 0;
}

Fügen Sie 1 für das Nullzeichen hinzu, et voila! 🙂

Bearbeiten, funktioniert auch für 2 Verschachtelungsebenen (GCC 4.8.0)

#include <stdio.h>
typedef struct {
    int length;
    char items[];     /* 1 */
} chararray_t;

typedef struct {
    long hash;
    chararray_t chars;   /* 2 */
} string_t;

typedef struct {
    long number;
    string_t arr;
}experiment_t;

static experiment_t s1 =
{
    617862378,
    {786,{6,"Hello"}} /* 3 */
};

int main()
{
    printf("%d %d %d %s\n",s1.number,s1.arr.hash,s1.arr.chars.length,s1.arr.chars.items);
    return 0;
}

———-BEARBEITEN 2—————— Es wurde ein Weg gefunden, um die Einschränkung zu umgehen, dass C ein Array innerhalb einer Struktur initialisiert

Endgültiger Code::

#include <stdio.h>
typedef struct {
    int length;
    int *items;     /* 1 */
} intarray_t;

typedef struct {
    long hash;
    intarray_t chars;   /* 2 */
    int dummy[2];
} string_t;

/*string_t s1 =
{
    617862378,
    {
        6,
        {1,2,3,4,5,6}
    },
    {
        0,0
    }
};*/

string_t s1 = {617862378,{},{0,0}};

int main()
{
    int i=0;
    intarray_t  t1 = {.length = 6, .items = (int[6]){1,2,3,4,5,6}};
    s1.chars = t1;
    printf("%d %d\n",s1.hash,s1.chars.length);
    while(i<s1.chars.length)
    {
        printf("%d",s1.chars.items[i]);
        i++;
    }
    putchar('\n');
    return 0;
}

  • Dies wird auf gcc (4.7.2) kompiliert, gibt jedoch auf Clang (425.0.27) die gleiche Fehlermeldung “Initialisierung eines flexiblen Array-Mitglieds ist nicht zulässig” aus.

    – Alex Gaynor

    15. April 2013 um 14:38 Uhr

  • Wir behalten unter anderem die Stringlänge bei, damit wir sie nicht mit Null terminieren müssen.

    – fijal

    15. April 2013 um 14:46 Uhr

  • Wir beenden sie nicht mit Null, aber wenn wir “abc” schreiben, glaube ich, dass C bereits das Zeichen “\0” hinzufügt, aber Sie haben Recht. Wir können die +1 von der Länge abziehen 🙂

    – Binayaka Chakraborty

    15. April 2013 um 14:50 Uhr

  • Außerdem funktioniert es für eine Verschachtelungsebene. Können Sie zwei Verschachtelungsebenen erstellen? Oder können Sie es zum Laufen bringen, wenn es int ist[] statt char[]?

    – fijal

    15. April 2013 um 14:55 Uhr

  • Siehe oben, es funktioniert auch für zwei Verschachtelungsebenen. Ich weiß das: Sie können keine Strukturen verwenden, die ein flexibles Array-Mitglied in einem Array (der Struktur) enthalten (siehe C99-Standard §6.7.2.1/2:), aber anscheinend unterstützt GCC dies, wenn der unbekannte Typ am Ende steht wenn die Strukturdeklaration. int[] geht nicht 🙁

    – Binayaka Chakraborty

    15. April 2013 um 15:35 Uhr

Benutzer-Avatar
renejsum

Ich nehme an, es gibt einen Grund, die Zeichenfolge “innerhalb” der Struktur zu halten, und Sie möchten ein Zeichen speichern, indem Sie nicht mit einer C-Zeichenfolge initialisieren.

Aber wenn nicht, könnten Sie Folgendes tun:

typedef struct {
    int length;
    char *items;     /* 1 */
} chararray_t;

typedef struct {
    long hash;
    chararray_t chars;   /* 2 */
} string_t;

static string_t s1 = {
    617862378,
    { 5, "Hell" }  /* 3 */
}; 
s1.chars.items[4] = 'o' ;

Sieht so aus, als könnten Sie den Union-Trick ausführen, aber stattdessen mit einer Typumwandlung?

#include <stdio.h>

typedef struct { int length; int items[]; } wchararray_t;
typedef struct { long hash; wchararray_t chars; } string_t;

typedef struct { int length; int items[5]; } wchararray_len5_t;
typedef struct { long hash; wchararray_len5_t chars; } string_len5_t;

static union { string_len5_t a; string_t b; } s5 = {
    617862378,
    { 5, { 'H', 'e', 'l', 'l', 'o' } }
};

string_t *s1 = (string_t*) &s5 ;

int main( int argc, char *argv[])
{

  for( int i = 0 ; i < s1->chars.length ; i++ )
    {
      printf ( "%c", s1->chars.items[i] );
    }
  printf( "\n" );
}

1345540cookie-checkDynamisches Array und vorgefertigte Daten

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy