Forum: Compiler & IDEs ARM GCC Problem Größe übergeordneten struct bestimmen


von Ingo S. (ingo-s)


Lesenswert?

Hi,
habe das Problem die wahre Größe einer übergeordneten struct 
herauszufinden, in der structs mit Strings enthalten sind.
Ich benötige die Größe, da der Inhalt über die serielle Schnittstelle 
gesendet werden muß.
Target ist STM32 Core0/Core3 mit GCC 4.7.4

//in def_struct.h Definitionen der mehrfach verwendeten structs
struct Ebene3{
  uint8_t  size;
  uint8_t  type;
  char    name[];  /* hier ist die Länge des Strings noch nicht bekannt! 
*/
};

struct Ebene4{
  uint8_t  size;
  uint8_t  type;
  uint8_t  offslng;
  uint8_t option_class;
  char  name[]; /* hier ist die Länge des Strings noch nicht bekannt! */
};
struct Head{
  uint8_t  size;
  uint8_t  type;
};

//---------

// in funktion.h wird die übergeordnete struct definiert:
struct SENSOR_PARA_DESCR{
    struct Ebene3 SensorZelle;
    struct Ebene4 UminZelle;
    struct Ebene3 SensorStrom;
    /* ... weitere structs */
    struct Head EndOfDescr1;
};

// in funktion.c wird der struct nun angelegt und initialisiert

#pragma pack(push)
#pragma pack(1)

const struct SENSOR_PARA_DESCR Sensor_Para_Descr = {
    .SensorZelle = {
      sizeof(Sensor_Para_Descr.SensorZelle)+sizeof("Zelle minV")
      ,PARDSC_MEASURE
      ,"Zelle minV"
      }
    ,.UminZelle = {
        sizeof(Sensor_Para_Descr.UminZelle)+sizeof("")
        ,PARDSC_MSBDATA
        ,ofs(0)              // Parameter offset
        ,OPTBIT_NON | MPXWK_VOLT    // Messwert 0.1Volt
        ,""
        }
    ,.SensorStrom = {
        sizeof(Sensor_Para_Descr.SensorStrom)+sizeof("Strom I")
        ,PARDSC_MEASURE
        ,"Strom I"
        }
    /* ... weitere structs */

    ,.EndOfDescr1 = {
      sizeof(Sensor_Para_Descr.EndOfDescr1)
      ,PARDSC_END_OFD
    }
};

const uint8_t Sensor_EndOf_Para_Descr = 0;
#pragma pack(pop)

// in Sendefunktion
liefert sizeof(Sensor_Para_Descr) eine zu kleine size, die strings sind 
nicht berücksichtigt.

Nächster Versuch über die Adressen des Inhalts zur Laufzeit weiter zu 
kommen:
  txdata_count = (void*)&Sensor_Para_Descr.EndOfDescr1 - 
(void*)&Sensor_Para_Descr;
klappt auch nicht, die Adresse des letzten internen structs wird auch 
falsch (ohne die Strings zu Berücksichtigen) übergeben.

Ohne eingeschaltete Optimierung funktioniert der Trick mit einer 
anschliessenden const Variablen hinter dem struct:
  txdata_count = (void*)&Sensor_EndOf_Para_Descr - 
(void*)&Sensor_Para_Descr;
mit Optimierung wird die variable Sensor_EndOf_Para_Descr aber vor den 
struct gelegt, geht also auch nicht.

Was nun? Mache ich hier einen Fehler, gibt es Tipps von den GCC Gurus 
hier?

Im Voraus vielen Dank
Ingo

von Peter II (Gast)


Lesenswert?

das kann so nicht gehen:

char    name[];

ist ein Zeiger. Die Daten liegen also überhaupt nicht innerhalb der 
Struct.

Damit ist auch klar das ein Sizeof - was ja zur compilezeit ausgewertet 
wird nicht wissen kann wie groß die Daten sind.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Peter II schrieb:
> das kann so nicht gehen:
>
> char    name[];
>
> ist ein Zeiger. Die Daten liegen also überhaupt nicht innerhalb der
> Struct.

Doch, tun sie.  Und es ist kein Zeiger.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Johann L. schrieb:
> Doch, tun sie.  Und es ist kein Zeiger.

Und "legal" sind sie auch:

http://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wobei es sich syntaktisch um "flexible array members" von C99 handelt 
und GCC eine etwas erweiterte Syntax / Semantik erlaubt.

von Ingo S. (ingo-s)


Lesenswert?

Gibt es nicht eine Compiler Anweisung, die auch bei Optimierung dem 
Compiler anweist, die anschliessende const Variable hinter dem struct, 
dort zu belassen und nicht beliebig wo anders hin zu legen? Damit wäre 
ja schon mein Problem gelöst.

Gruß Ingo

von Rolf Magnus (Gast)


Lesenswert?

Ingo Stahl schrieb:
> Gibt es nicht eine Compiler Anweisung, die auch bei Optimierung dem
> Compiler anweist, die anschliessende const Variable hinter dem struct,
> dort zu belassen und nicht beliebig wo anders hin zu legen?

Er legt sie nicht wo anders hin. Die Variable hat aber eine Größe von 0, 
kann also keine Daten aufnehmen.

Ingo Stahl schrieb:
> // in Sendefunktion
> liefert sizeof(Sensor_Para_Descr) eine zu kleine size, die strings sind
> nicht berücksichtigt.

Natürlich nicht. Die Größe eines Datentyps ist fix. Die kann nicht mit 
jeder Instanz anders sein. Die Größe steht fest, wenn du den Typ 
definierst, nicht erst wenn du eine Instanz anlegst.

von Ingo S. (ingo-s)


Lesenswert?

Die variable hat die Größe uint8_t, also ein Byte mit dem Wert 0.
Das sie je nach Optimierungsstufe nicht immer hinter den structs liegt, 
kann man im Debugger beobachten.

Hier ist es ja so, das die string-size ja schon zur compile time bekannt 
ist, der GCC dies aber nicht verarbeitet und zur Laufzeit nicht die 
richtigen Adressen der internen structs liefert.

Und das finde ich besonders krass!

Um das Problem schnell aus der Welt zu schafffen habe ich einen 
Vierzeiler geschrieben, der zur Laufzeit einmal durch die structs geht, 
bis er den struct mit der ENDE-Kennung gefunden hat und die Länge des 
äußeren structs bereitstellt.

Gruß Ingo

von Rolf Magnus (Gast)


Lesenswert?

Ingo Stahl schrieb:
> Und das finde ich besonders krass!

Ich finde es krass, dass du die Warnungen ausgeschaltet hast oder 
ignorierst. Bei mir kommen zumindest überall, wo du diese Strukturen 
erzeugst, die Warnungen:

    Warnung: ungültige Verwendung einer Struktur mit flexiblem 
Feldelement

und:
    Warnung: Initialisierung eines flexiblen Feld-Elements

Lies mal:
http://gcc.gnu.org/onlinedocs/gcc-4.8.2/gcc/Zero-Length.html

von Yalu X. (yalu) (Moderator)


Lesenswert?

Ingo Stahl schrieb:
> Hier ist es ja so, das die string-size ja schon zur compile time bekannt
> ist, der GCC dies aber nicht verarbeitet und zur Laufzeit nicht die
> richtigen Adressen der internen structs liefert.
>
> Und das finde ich besonders krass!

Der Compiler kennt die String-Größe nicht immer. Stell dir vor, du hast
in einer anderen Quelldatei eine Extern-Verweis auf Sensor_Para_Descr.
Dort kann der Compiler zur Größenbestimmung nur die Struct-Deklaration
von SENSOR_PARA_DESCR, nicht aber die Variablendefinition heranziehen.

Würde der Compiler zwei unterschiedliche Größen liefern, je nachdem, ob
nur die Struct-Deklaration oder zusätzlich auch die Variablendefinition
bekannt ist, wäre das noch viel krasser.

Das eigentlich Problem liegt darin, dass die Initialisierung eines
flexible Array-Members durch den C-Standard nicht abgedeckt ist. Die
Verwendung einer Struktur mit einem flexible Array-Member als
nichtletztes Element einer übergeordneten Struktur ist nach meinem
Verständnis zwar erlaubt, ergibt aber wenig Sinn. Wie schon von Rolf
angemerkt, erntest du entsprechende Warnungen, wenn du dem Compiler die
Option -pedantic mitgibst.

Der eigentliche Sinn der flexible Array-Members besteht darin, dass man
bei dynamisch (mit malloc) erzeugten Strukturen ein einzelnes, ganz am
Ende der Struktur stehendes Array variabel gestalten kann. Dazu muss
beim malloc-Aufruf die Größe der Struktur plus die gewünchte Array-Größe
als Argument übergeben werden.

GCC erlaubt auch die Verwendung und die Initialisierung von flexible
Array-Members in normalen Variablen, aber auch hier funktioniert das nur
dann korrekt, wenn das Array ganz am Ende der Struktur steht.

von Ingo S. (ingo-s)


Lesenswert?

Auf der anderen Seite kennt der Compiler die wahre Größe zum Schluss 
dann doch, wenn er weiteren Code oder const Daten hinter die äußere 
structur packt.

Die gleichen Structuren habe ich schon beim WinAVR eingesetzt, nur ohne 
die flexiblen structuren in eine äußere einzupacken. Dort hatte ich auch 
schon festgestellt, das sizeof(mystruct) nicht funktioniert, aber die 
Adressen der nahtlos definierten und initialisierten structuren stimmten 
zumindest.
Beim STM32 hatte ich dann das Problem, das trotz #pragma pack(1) 
zwischen den Structuren dann doch ein Alignment greifte, was ich nicht 
gebrauchen kann und das ich dann durch Integration in eine übergeordnete 
Struktur lösen konnte.

Gruß Ingo

von Williwilliwallawalla (Gast)


Lesenswert?

>Das eigentlich Problem liegt darin, dass die Initialisierung eines
>flexible Array-Members durch den C-Standard nicht abgedeckt ist.

Verzeihung, wenn ich hier eine Seitenfrage stelle.
Ist das einfach nicht erwähnt oder steht das ausdrücklich im Standard?
Leider habe ich nur die Drafts.
Steht darüber was im K&R? (Konnten leider nichts dazu finden).
Gibt es ein anderes Buch, wo man das nachlesen könnte?

Wäre nett. Ich würde das sehr gerne mal nachlesen.

von Ingo S. (ingo-s)


Lesenswert?

Das erste was mich in diesem Zusammenhang wunderte das bei:

struct Ebene4{
  uint8_t  size;
  uint8_t  type;
  uint8_t  offslng;
  uint8_t option_class;
  char  name[];
};

mit der Initialisierung zur compile time z.B.:

const struct Ebene4 UminZelle = {
  sizeof(UminZelle)
  ,1
  ,2
  ,3
  ,"meinText"
};

sizeof(UminZelle) zum Eintrag der wahren Größe nicht funktioniert, wobei 
doch alle Informationen zu diesem Zeitpunkt bereits vorhanden sind.
Und man händisch sizeof(meinText) noch dazu addieren muss damit es 
stimmt:

const struct Ebene4 UminZelle = {
  sizeof(UminZelle)+sizeof(""meinText")
  ,1
  ,2
  ,3
  ,"meinText"
};

Der Compiler vom VC6++ machte das aber korrekt, wenn ich mich richtig 
erinnere.

Gruß Ingo

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Was berechnert er denn? sizeof(UminZelle) sollte 4 sein.

von Ingo S. (ingo-s)


Lesenswert?

Genau, er liefert 4, die Länge vom String unterschlägt er obwohl sie 
eigentlich bekannt sein sollte. Oder geht der GCC nur einmal über die 
Sourcen und kann keine forward Referenzen wie ein Makro Assembler?

Gruß Ingo

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Ingo Stahl schrieb:
> Genau, er liefert 4, die Länge vom String unterschlägt er obwohl sie

Er unterschlägt garnix.  Für sizeof wird der Typ des Datums bestimmt und 
dann dessen Größe anhand des ABI bestimmt (Alignment, Gaps, etc.). 
"struct Ebene4" ist unb bleibt ein incomplete Type der Größe 4, egal 
welche Objekte davon später erzeugt werden.

Ich erlaube mir, aus dem C99-Standard zu zitieren (§6.7.2.1 #16)

>> As a special case, the last element of a structure with more
>> than one named member may have an incomplete array type;
>> this is called a flexible array member. In most situations,
>> the flexible array member is ignored. In particular, the size
>> of the structure is as if the flexible array member were omitted
>> except that it may have more trailing padding than the omission
>> would imply.


> eigentlich bekannt sein sollte. Oder geht der GCC nur einmal über die
> Sourcen und kann keine forward Referenzen wie ein Makro Assembler?

C ist kein Makro Assembler und GCC ist es auch nicht.

Übrigens machen C / C++ auch keine Aussage über die Ablage / Reihenfolge 
von Objekten im Speicher.  Auch wenn eine bestimmte Reihenfolge 
beobachtet wurde, bedeutet dies nicht, dass sie immer so bleiben wird. 
Aber das hast du ja schon selbst festgestellt...

: Bearbeitet durch User
von Yalu X. (yalu) (Moderator)


Lesenswert?

Williwilliwallawalla schrieb:
>>Das eigentlich Problem liegt darin, dass die Initialisierung eines
>>flexible Array-Members durch den C-Standard nicht abgedeckt ist.
>
> Verzeihung, wenn ich hier eine Seitenfrage stelle.
> Ist das einfach nicht erwähnt oder steht das ausdrücklich im Standard?
> Leider habe ich nur die Drafts.

Die Drafts reichen.

Hier ist der entsprechende Auszug aus dem C11-Draft, aus dem hervorgeht,
was alles erlaubt ist und was nicht, und wie die Größe des Structs
definiert ist:
1
As a special case, the last element of a structure with more than one
2
named member may have an incomplete array type; this is called a
3
flexible array member. In most situations, the flexible array member is
4
ignored. In particular, the size of the structure is as if the flexible
5
array member were omitted except that it may have more trailing padding
6
than the omission would imply. However, when a . (or ->) operator has a
7
left operand that is (a pointer to) a structure with a flexible array
8
member and the right operand names that member, it behaves as if that
9
member were replaced with the longest array (with the same element type)
10
that would not make the structure larger than the object being accessed;
11
the offset of the array shall remain that of the flexible array member,
12
even if this would differ from that of the replacement array. If this
13
array would have no elements, it behaves as if it had one element but
14
the behavior is undefined if any attempt is made to access that element
15
or to generate a pointer one past it.

Danach kommen ein paar Beispiele, u.a. dieses, wo die Initialisierung
des flexible Array-Member explizit als invalid gekennzeichnet ist:
1
struct s t1 = { 0 };         // valid
2
struct s t2 = { 1, { 4.2 }}; // invalid
3
t1.n = 4;                    // valid
4
t1.d[0] = 4.2;               // might be undefined behavior

Der GCC hält sich genau an diese Vorgaben, allerdings sollte er IMHO bei
der invaliden Initialisierung einen Error statt einer Warning ausgeben.

Wenn VC6 die Größe anders bestimmt, ist das ein Bug.

von Williwilliwallawalla (Gast)


Lesenswert?

@ Yalu

Dankeschön.

von Ingo S. (ingo-s)


Lesenswert?

@ yalu
vielen Dank für die ausführliche Erklärung.
Bei zur Laufzeit angelegten Structuren kann ich die Einschränkungen ja 
durchaus verstehen. Zur Compile time, wenn alle notwendigen 
Informationen für ein const Objekt vorliegen, hatte ich mit solchen 
Einschränkungen aber nicht gerechnet.

Habe ich die Aufgabenstellung eventuell falsch umgesetzt, es gibt sicher 
noch andere Wege nach Rom?

Ich benötige einen Datensatz (zum senden an ein externes Gerät), der im 
Flash liegt, aus 20 bis 30 flexiblen structuren besteht, die nahtlos 
hintereinander liegen.

eine dieser flexiblen structuren hat immer den Aufbau:

struct flex{
  uint8_t  size;
  uint8_t  type;
  uint16_t maxwert;
  /* ... weitere variablen */
  char  name[];
};

Im Kopf steht immer die Größe und dahinter der Typ, damit ist der 
Empfänger des Datensatzes in der Lage von Struktur zur Struktur zu gehen 
und diese in Verbindung mit den Struktur Definitionen entsprechend 
auszuwerten.

Gruß Ingo

von Eduard S. (schneehase)


Lesenswert?

Ich würde es in etwa so machen.
1
#define ADC0Type     0x3B     // Name für Programm
2
char    ADC0name[] = "ADC0";  // Name für User
3
4
struct flex
5
{
6
  uint8_t  size;     // max 256 byte
7
  uint8_t  type;     // max 256 Typen
8
  uint16_t maxwert;  // maximaler Wert von payload
9
  uint16_t payload;  //
10
  char     *name;    // null terminierter String
11
};
12
13
uint8_t Low( uint16_t u16wert )
14
{
15
  return (( u16wert >> 0 ) & 0xFF);
16
}
17
18
uint8_t High( uint16_t u16wert )
19
{
20
  return (( u16wert >> 8 ) & 0xFF);
21
}
22
23
void SendByte( uint8_t byte )
24
{
25
  UsartSendByte(byte);
26
}
27
28
void SendWord( uint8_t word )
29
{
30
  SendByte( Low( word) );
31
  SendByte( High(word) );
32
}
33
34
void SendFlex( struct flex *fl )
35
{
36
  char *name = fl->name;
37
38
  SendByte( fl->size );
39
  SendByte( fl->type );
40
  SendWord( fl->maxwert );
41
  SendWord( fl->payload );
42
  for( ; *name!=0; name++ )
43
  {
44
    SendByte( *name );
45
  }
46
  SendByte( 0 );
47
}
48
49
int main(void)
50
{
51
  struct flex ADC0 = {
52
    .type    = ADC0Type,
53
    .maxwert = 256,
54
    .name    = ADC0name,
55
  };
56
57
  // Größe der struct berechnen
58
  // size    = 1 Byte
59
  // type    = 1 Byte
60
  // maxwert = 2 Byte(s)
61
  // payload = 2 Byte(s)
62
  // Summe von flex = 6  Bytes
63
64
  // strlen  = 4
65
  // +1 für die Null -> 5
66
  // Summe gesammt  = 6+5 = 11 Bytes
67
  // Entweder mit sizeof
68
  ADC0.size    = sizeof(struct flex) + strlen(ADC0.name) + 1;
69
  // oder statisch machen
70
  ADC0.size    = 6 + strlen(ADC0.name) + 1;
71
72
  while( 1==1 )
73
  {
74
    // Messung eines analogen Wertes von ADC0
75
    ADC0.payload = getADC0();
76
    // Wert Senden
77
    SendFlex( &ADC0 );
78
  }
79
80
  return 0;
81
}

von Ingo S. (ingo-s)


Lesenswert?

Vielen Dank für die Mühe.
Da ich aber 20 bis 30 dieser Strukturen habe, ist diese Art "zu Fuss" zu 
übertragen viel zu aufwendig.
Es geht auch darum die vielen Strukturen möglichst einfach anlegen zu 
können, ohne die Länge von Strings "händisch" auszählen zu müssen.
Adresse und Länge über alles reicht zum senden.

Mit der jetzigen Lösung werden die Strukturen in der übergeordneten 
Struktur ja richtig angelegt, es ging nur noch um die size bzw. Länge 
über alles für die Senderoutine, die alles am Stück in einer ISR 
rausschickt.
Mit einer kleinen Schleife gehe ich nun einmal über die strukturen und 
ermittle diesen fehlenden Wert.

Gruß Ingo

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.