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
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]
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.
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
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.
Meinst du für
items[]
ein Array von seinint
?– 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