(erledigt)[C++]struct auf zu kleinem Puffer standardkonform?

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

(erledigt)[C++]struct auf zu kleinem Puffer standardkonform?

Beitrag von Krishty »

Sagen wir, ich habe einen Puffer von zwei Bytes Größe:

  void * twoBytes;

Ich habe außerdem folgendes struct:

  struct S {
    int16_t first;
    int16_t second;
  };


Ich lege S über den Puffer. (Alignment hier außen vor.) Ein Zugriff auf second ist gewiss verboten und führt mit großer Wahrscheinlichkeit zum Absturz. Aber ist es zumindest erlaubt, auf first zuzugreifen?

  auto foo = (S*)twoBytes;
  printf("%i", foo->first);


Ich frage, weil ich das Speicherabbild einer Datei bekomme, über das ich ein struct legen will. Auf die Felder möchte ich nur zugreifen, sofern die Datei groß genug ist. Laufe ich dabei Gefahr, dass ein Compiler die Prüfung der Länge wegoptimiert, weil er annehmen darf, dass file zumindest sizeof(S) groß ist, bloß, weil ich dazu gecastet habe? Oder weil ich ein beliebiges Feld daraus dereferenziere? Oder erst, wenn ich auf Felder mit entsprechendem Offset zugreife?

  void process(void * file, size_t size) {
    auto data = (S*)file;
    if(size >= 2) printf("%i", file->first);
    if(size >= 4) printf("%i", file->second);
  }
Zuletzt geändert von Krishty am 09.10.2016, 02:24, insgesamt 3-mal geändert.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [C++] struct auf zu kleinem Puffer standardkonform?

Beitrag von dot »

Ohne da jetzt im Detail nachgeschaut zu haben, würd ich mal sagen, dass du in dem Moment, wo du über einen S* auf twoBytes zuzugreifen versuchst, bereits undefined behavior hast und alles weitere sich in Bezug auf Standard C++ damit erübrigt... ;)
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] struct auf zu kleinem Puffer standardkonform?

Beitrag von Krishty »

Stimmt; und es vereinfacht die Frage weiter, wenn wir einfach über einen void * sprechen, der zu S gecastet wird. Ich editier mal.

So. Meine Einschätzung: Da muss ich mir keine Gedanken um aggressive Optimierung machen.

Gehen wir vom pessimistischen Fall aus: Dass der C++-Standard irgendwo irgendwie vorschreibt, dass hinter einem S * alle Member gültig zugreifbar sind, so lange der Zeiger nicht nullptr ist.
  1. data wird benutzt, ohne vorher auf nullptr geprüft zu werden
  2. also ist die Annahme data != nullptr gültig
  3. also muss auch jedes andere Member von data gültig zugreifbar sein
Meine Befürchtung war, dass deshalb der Größenvergleich wegoptimiert wird. Das ist aber totaler Quatsch, weil der Compiler kein Konzept davon hat, dass size tatsächlich was mit file zu tun hat. Es könnte, was ihn betrifft, auch das Ergebnis von rand() sein. Typischer Fall von „viel zu abstrakt gedacht“.

Um das beobachtbare Verhalten zu erhalten, darf er also die Vergleiche nicht wegoptimieren. So einfach ist das.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: (erledigt)[C++]struct auf zu kleinem Puffer standardkonf

Beitrag von dot »

Ich frag mich halt: Wieso muss es unbedingt über dieses S gehen? Ein vermutlich standardkonformer Weg:

Code: Alles auswählen

void process(const char* file, size_t size) {
    if(size >= 2) {
        std::int16_t first;
        std::memcpy(&first, file, sizeof(first));
        printf("%i", first);
    }
    if(size >= 4) {
        std::int16_t second;
        std::memcpy(&second, file, sizeof(second));
        printf("%i", second);
    }
}
DerAlbi
Establishment
Beiträge: 269
Registriert: 20.05.2011, 05:37

Re: (erledigt)[C++]struct auf zu kleinem Puffer standardkonf

Beitrag von DerAlbi »

Ich finde das explizite interpretieren eines Speichers ist eine ganz normale Operation.
Ob man nun Speicher typisiert dereferenziert, oder per memcpy darauf zugreift ist vollkommen Wurst, solange man nur Speicher beackert, der einem selbst gehört. Das problem mit memcpy ist, dass es unnutzer Overhead ist, falls man einfach nur read-only-Zugriff hat. Wenn es legal ist auf den Speicher zuzugreifen, muss ich ihn nicht erst kopieren, um ihn zu lesen, weil beim kopieren eh schon gelesen wird. Beim Schreiben ist es die selbe Geschichte.

Was mich an der Stelle wundert: wenn in deine Struktur Padding reingehauen wird, kannst du dich leider nicht mal mehr aufs speicherlayout verlassen. Und das obige Beispiel sollte eigentlich Padding enthalten (können). Irgendwie sollte da ein packed-attribut an die struct geklatscht werden.
Das einzig Wichtige wo es wirklich lichterloh brennen kann ist ein nicht an typgrenzen ausgerichteter Zugriff. Aber
Alignment hier außen vor
Go for it! :D

Code: Alles auswählen

if(size >= 2) printf("%i", file->first);
if(size >= 4) printf("%i", file->second);
Ich würde der Flexibilität wegen die magic numbers raushauen und offsetof(..) + sizeof(..) verwenden (da haut dir dann Padding auch nix kaputt, falls du doch mal für eine andere Platform compilierst).
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: (erledigt)[C++]struct auf zu kleinem Puffer standardkonf

Beitrag von Krishty »

@dot: Wie Albi sagt, ist das mit memcpy() furchtbare Quälerei, vor allem mit der Zeigerarithmetik. Ich will nicht die Offsets von 70 Variablen hard-coden müssen bevor ich sie auslese. (Du hast ja z.B. auch den Fehler drin, ein zweites Mal auf first zuzugreifen statt auf second, weil ein + 2 fehlt.)

memcpy() ist zwar nicht mehr soo viel Overhead, weil es fast alle Compiler zu einem einzelnen Load optimieren würden, aber es verschwendet noch immer Speicher auf dem Stack und verhindert die Nutzung kurzer Befehle. (Aus add rax, word ptr [file + 8] würde mov word ptr [rsp+60], word ptr [file + 8] / add rax, word ptr [rsp+60] weil du nun eine Variable hast, deren Adresse beschrieben wird.)

@Albi: Padding kontrolliere ich, nur war das für die Frage total egal, wie Alignment halt auch. offsetoff(x) + sizeof(x) nutze ich auch; danke – aber auch das hätte die Frage einfach nur verkompliziert.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Spiele Programmierer
Establishment
Beiträge: 426
Registriert: 23.01.2013, 15:55

Re: (erledigt)[C++]struct auf zu kleinem Puffer standardkonf

Beitrag von Spiele Programmierer »

Nun, der Standard garantiert da wohl nix.
Mit Visual C++ sollte diese Operation aber trotzdem funktionieren, weil es kein Strict Aliasing kennt.
Für andere Compiler gibt es __attribute__((may_alias)).

Code: Alles auswählen

#if defined(__GNUC__) || defined(__llvm__) || defined(__clang__)
#define MAY_ALIAS __attribute__((may_alias))
#elif defined(_MSC_VER)
#define MAY_ALIAS 
#else
#error MAY_ALIAS is not implemented for this compiler
#endif

//...

 void process(void * file, size_t size) {
    auto data = (S MAY_ALIAS*)file;
    if(size >= 2) printf("%i", file->first);
    if(size >= 4) printf("%i", file->second);
  }
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: (erledigt)[C++]struct auf zu kleinem Puffer standardkonf

Beitrag von Krishty »

Hat das wirklich was mit Strict Aliasing zu tun? Bei dem Cast von void * muss der Compiler annehmen, dass an der Stelle wirklich ein S liegt. Sonst wäre ja z.B. malloc() eine ewige Verletzung von Strict Aliasing.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
DerAlbi
Establishment
Beiträge: 269
Registriert: 20.05.2011, 05:37

Re: (erledigt)[C++]struct auf zu kleinem Puffer standardkonf

Beitrag von DerAlbi »

[Ich bin zu blöd überhaupt das Problem zu verstehen.] das hat sich beim schreiben des post wohl langsam aufgeklärt.
Eine Struktur S liegt im speicher. Ein Memerzugriff geschieht indem: (jetzt mal ohne Pointerarithmetik gedacht und einfach nur Adressen addieren)

Code: Alles auswählen

*(&S + offsetof(S.Member))
solang der Speicher mir gehört - was soll da schiefgehen und was konkret soll der Standard dagegen haben auf eine Membervariable zuzugreifen..
Aliasing ist das einzige, was mir in den Sinn kommt. Prinzipiell ist die Operation

Code: Alles auswählen

S* s = (S*)WasVoidPtr;
nicht erwünscht. Es ist UB. Der Compiler kann nun alles totoptimieren, denn alle Operationen auf der Struktur haben offiziell keinen Einfluss auf Speicher, der nicht der Struktur gehört. Mit anderen Worten, jeder Einfluss auf S wird nicht auf einen Einfluss auf WasVoidPtr bezogen.

In dem Fall hilft volatileüberall. Aber das macht mehr tot als man will. Wie wäre es mit einer MemoryBarrier(ist das der richtige begriff?)
In gcc kann man schreiben:

Code: Alles auswählen

asm volatile (""  ::: "memory");

Das bedeutet dann, das sich absolut jeder Speicher geändert hat. [Stimmt das? ]
Das killt die Optimierung nur lokal und ist quasi das, was du willst, wenn man mit Aliasing arbeit.

Edit:
Ich bin mit mit der MemoryBarrier grade nicht mehr sicher, ob die tut, was ich sage.
Ich bin letztlich grade auf der Suche nach so einer Art touch(WasVoidPtr) was sagt, alle annahme über den Speicher hinter der Variable verworden werden sollen.
asm volatile ("" ::: "memory") wird als "compiler barrier" bezeichnet. Das geht schon in die richtige Richtung.

Edit2:
Tadaaah:
Unless *ptr and vobj can be aliased, it is not guaranteed that the write to *ptr occurs by the time the update of vobj happens. If you need this guarantee, you must use a stronger memory barrier such as:

Code: Alles auswählen

     int *ptr = something;
     volatile int vobj;
     *ptr = something;
     asm volatile ("" : : : "memory");
     vobj = 1;
Dein Job: MSVC äquivlanet finden und gut.
Zuletzt geändert von DerAlbi am 09.10.2016, 13:03, insgesamt 2-mal geändert.
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: (erledigt)[C++]struct auf zu kleinem Puffer standardkonf

Beitrag von dot »

volatile an sich ändert hier nix dran, dass es UB is und asm volatile ist kein Standard C++.
Krishty hat geschrieben:memcpy() ist zwar nicht mehr soo viel Overhead, weil es fast alle Compiler zu einem einzelnen Load optimieren würden, aber es verschwendet noch immer Speicher auf dem Stack und verhindert die Nutzung kurzer Befehle. (Aus add rax, word ptr [file + 8] würde mov word ptr [rsp+60], word ptr [file + 8] / add rax, word ptr [rsp+60] weil du nun eine Variable hast, deren Adresse beschrieben wird.)
Also clang macht mir aus beiden Varianten den exakt selben Machinencode... ;)


Und was die Quälerei angeht:

Code: Alles auswählen

template <typename T>
T read(const char*& file)
{
  T t;
  std::memcpy(&t, file, sizeof(T));
  file += sizeof(T);
  return t;
}

void process(const char* file, size_t size) {
    if(size >= 2) {
        auto first = read<std::int16_t>(file);
        printf("%i", first);
    }
    if(size >= 4) {
        auto second = read<std::int16_t>(file);
        printf("%i", second);
    }
}
;)
DerAlbi
Establishment
Beiträge: 269
Registriert: 20.05.2011, 05:37

Re: (erledigt)[C++]struct auf zu kleinem Puffer standardkonf

Beitrag von DerAlbi »

volatile an sich ändert hier nix dran, dass es UB is und asm volatile ist kein Standard C++.
volatile verhindert die Auswirkungen des UB indem keine fälschlichen Optimierungen angewendet werden, weil jeder Speicherzugriff als unoptimierter Speicherzugriff geschieht, hat das Program keine Chance trotz Aliasing nicht zu funktionieren.
Mit anderen Worten: man hebelt den Mechanismus aus, mit dem Aliasing Probleme macht.

Was jetzt an inline-assember nicht konform ist, verstehe ich leider auch nicht. :-(

Code: Alles auswählen

DomeSomethingOnVoidPtr(..)
asm ("":::"memory");
S* s = (S*)WasVoidPtr;
DoWeirdStuffOnS(s);
asm ("":::"memory");
DomeSomethingOnVoidPtr(..)
Zeigt meiner These nach kein UB, da das UB durch die Optimierung entsteht, die der Compiler aufgrund von Aliasing-Rules macht, die durch die Barriere explizit außer kraft gesetzt werden.
Selbst wenn die Barriere nur das Reordering der Speicherzugriffe verhindert, wird damit auch die Optimierung gekillt, weil alle Speicherzugriffe abgeschlossen werden müssen.
Der Compiler kann dann

Code: Alles auswählen

Speicher = abc;
Barrier();
xyz = Speicher;
nicht mehr zu xyz = abc optimieren.

In dem Zusammenhang finde ich Spiele Programmierers Antwort optimal. Ich glaube, dass ist das, was Kirshty eigentlich will. Das zerstört auch weniger Optimierung als meine Compiler-Barriere. Die tötet ja wirklich alles.
Zuletzt geändert von DerAlbi am 09.10.2016, 13:06, insgesamt 1-mal geändert.
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: (erledigt)[C++]struct auf zu kleinem Puffer standardkonf

Beitrag von Krishty »

DerAlbi hat geschrieben:Prinzipiell ist die Operation

Code: Alles auswählen

S* s = (S*)WasVoidPtr;
nicht erwünscht. Es ist UB. Der Compiler kann nun alles totoptimieren, denn alle Operationen auf der Struktur haben offiziell keinen Einfluss auf Speicher, der nicht der Struktur gehört.
Nein, das stimmt so nicht:
  1. void *S * und umgekehrt sind, Wirkung des Zugriffs außen vor, erlaubt (Quelle)
  2. char *void * und umgekehrt ebenfalls (selbe Quelle wie oben)
  3. Strict Aliasing erlaubt, jeden Typ mit char * zu aliasen (Quelle)
  4. es ist egal, wie man an den char * auf ein Objekt kommt, solange man dabei keine Regeln der Sprache verletzt
  5. genau so rechtfertigt der Standard die Funktion von memcpy(): S *void * bei Parameterübergabe; memcpy() castet das intern zu char * und arbeitet darauf, was gemäß Strict Aliasing explizit erlaubt ist (wackelige Quelle)
Die Casts sind also gültig und jede Operation, die ich auf S * durchführe, spiegelt sich auch mit Strict Aliasing in jedem char * wieder. Was man allerdings nicht tun darf, ist, durch diese Casts den dynamischen Typ zu ändern:
  1. float *char * ist erlaubt
  2. char *int * ebenfalls
  3. das Schreiben oder Lesen durch int * ist allerdings verboten, wenn vorher durch float * geschrieben oder gelesen wurde – klassisches UB
  4. das Schreiben und Lesen durch int * ist nicht verboten, wenn vorher durch char * geschrieben oder gelesen wurde – Strict Aliasing erlaubt char *-Aliasing
Wenn ich nun aus einem Systemaufruf einen void * oder char * bekomme (wie beim Abbilden einer Datei in den Speicher), dann darf ich zu S * casten. Ich darf auch durch S * auf dem Speicher arbeiten, falls er vorher ausschließlich durch char * beackert wurde und durch keinen anderen Typ. Und alle Änderungen spiegeln sich auch ohne Memory Barrier in char * wieder, dafür muss der Compiler Sorge tragen.

These steht, jetzt suche ich die Quellen.

Und letztendlich spielt das eh keine Rolle weil meine Datei nur an einer Stelle gemappt ist und ich nur einen einzigen Zeiger darauf habe.
Zuletzt geändert von Krishty am 09.10.2016, 13:18, insgesamt 4-mal geändert.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Spiele Programmierer
Establishment
Beiträge: 426
Registriert: 23.01.2013, 15:55

Re: (erledigt)[C++]struct auf zu kleinem Puffer standardkonf

Beitrag von Spiele Programmierer »

Hat das wirklich was mit Strict Aliasing zu tun? Bei dem Cast von void * muss der Compiler annehmen, dass an der Stelle wirklich ein S liegt. Sonst wäre ja z.B. malloc() eine ewige Verletzung von Strict Aliasing.
Nun - entweder liegt an der Stelle ein Objekt S oder eben nicht.
Wenn der erste Fall zutrifft, erübrigt sich das Thema. Im zweiten Fall ist ein Strict Aliasing Problem.

malloc ist in Ordnung, weil der Speicher noch keinen Typ besitzt.
DerAlbi
Establishment
Beiträge: 269
Registriert: 20.05.2011, 05:37

Re: (erledigt)[C++]struct auf zu kleinem Puffer standardkonf

Beitrag von DerAlbi »

Danke für die Richtigstellung :-) Ein wichtiges Detail.
Jetzt mal rein aus interesse zum Thema:
Impliziert das nun aber nicht, dass du in der struct nur chars haben darfst?
Mir geht es um den angenommenen Schreizugriff auf deine gemappte Struktur. Wenn man z.B. per int scheibt kann man an anderer Stelle das nicht mehr auslesen, außer man setzt sich den int aus den einzelnen chars des Speichers zusammen? (wtf)

a) oder ist struct->int ailiasing mit IntArray[n] erlaubt, weils beides int sind ?)
b) struct->int ailiasing mit floatArray[n] ist nicht erlaubt, aber fürde ich den float als char* aus dem Speicher binär zusammensetzen ist das ok? (immernoch: wtf)
Zuletzt geändert von DerAlbi am 09.10.2016, 13:21, insgesamt 1-mal geändert.
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: (erledigt)[C++]struct auf zu kleinem Puffer standardkonf

Beitrag von Krishty »

Wenn du per int * reinschreibst, musst du entweder als int * wieder auslesen, oder als Folge von char (was ja z.B. memcpy() macht, weswegen dots Vorschlag 100 % konform ist).

Ich darf in meinem struct haben, was ich will. Aber wenn irgendwann mal anders auf die Daten zugreife als durch das struct, bleibt mir als einzige Möglichkeit char *. Oooooder ich schalte Strict Aliasing ab oder baue Memory Barriers ein etc, aber das ist dann nicht mehr portabel.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: (erledigt)[C++]struct auf zu kleinem Puffer standardkonf

Beitrag von Krishty »

dot hat geschrieben:
Krishty hat geschrieben:memcpy() ist zwar nicht mehr soo viel Overhead, weil es fast alle Compiler zu einem einzelnen Load optimieren würden, aber es verschwendet noch immer Speicher auf dem Stack und verhindert die Nutzung kurzer Befehle. (Aus add rax, word ptr [file + 8] würde mov word ptr [rsp+60], word ptr [file + 8] / add rax, word ptr [rsp+60] weil du nun eine Variable hast, deren Adresse beschrieben wird.)
Also clang macht mir aus beiden Varianten den exakt selben Machinencode... ;)
Cool, danke! Da pennt Visual C++ also mal wieder.
Und was die Quälerei angeht:
Neee – ich greife nicht seriell auf die Daten zu. Ich brauche echten wahlfreien Zugriff! Ich müsste alles in lokalen Variablen fetchen, in der richtigen Reihenfolge, aber dann habe ich 30 davon und keinen Mehrwert gegenüber einem struct-Overlay.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: (erledigt)[C++]struct auf zu kleinem Puffer standardkonf

Beitrag von Krishty »

DerAlbi hat geschrieben:a) oder ist struct->int ailiasing mit IntArray[n] erlaubt, weils beides int sind ?)
Puh, keine Ahnung. VIELLEICHT wäre es mit POD erlaubt, aber … uff.
DerAlbi hat geschrieben:b) struct->int ailiasing mit floatArray[n] ist nicht erlaubt, aber fürde ich den float als char* aus dem Speicher binär zusammensetzen ist das ok? (immernoch: wtf)
Ja, ist es. Ist der einzig portable Weg, float als int zu interpretieren (LOL). Bedenk, dass sich das aber einfacher als memcpy() ausdrücken lässt, weil das intern immer auf char * operiert.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
DerAlbi
Establishment
Beiträge: 269
Registriert: 20.05.2011, 05:37

Re: (erledigt)[C++]struct auf zu kleinem Puffer standardkonf

Beitrag von DerAlbi »

*_* Danke <3
:-D
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: (erledigt)[C++]struct auf zu kleinem Puffer standardkonf

Beitrag von dot »

Krishty hat geschrieben:Neee – ich greife nicht seriell auf die Daten zu. Ich brauche echten wahlfreien Zugriff! Ich müsste alles in lokalen Variablen fetchen, in der richtigen Reihenfolge, aber dann habe ich 30 davon und keinen Mehrwert gegenüber einem struct-Overlay.
Naja, im Gegensatz zu einem struct-Overlay wäre es portabel... ;)
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: (erledigt)[C++]struct auf zu kleinem Puffer standardkonf

Beitrag von Krishty »

Wo ist das struct nicht portabel?
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: (erledigt)[C++]struct auf zu kleinem Puffer standardkonf

Beitrag von dot »

struct Layout ist im Allgemeinen nicht portabel... ;)
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: (erledigt)[C++]struct auf zu kleinem Puffer standardkonf

Beitrag von Krishty »

Praktisch ist’s portabel genug.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: (erledigt)[C++]struct auf zu kleinem Puffer standardkonf

Beitrag von dot »

Krishty hat geschrieben:Die Casts sind also gültig und jede Operation, die ich auf S * durchführe, spiegelt sich auch mit Strict Aliasing in jedem char * wieder.
Das Problem sind ja auch nicht die Pointercasts (sofern man mal davon ausgeht, dass alle involvierten Typen kompatible Alignment Requirements haben), sondern was du mit dem gewonnenen Pointer dann anstellst. Du darfst eben nur über einen aus char* (oder void*, das macht hier eigentlich keinen Unterschied) gewonnenen S* auf ein S an dieser Stelle zugreifen, wenn sich dort tatsächlich ein S befindet. Ein S befindet sich dort nur, wenn zuvor dort ein S konstruiert wurde...

Die Lösung per memcpy() funktioniert, weil es sich um trivially-copyable Types handelt und der Standard mir garantiert, dass ich die sizeof(T) Bytes der Object-Representation eines trivially-copyable Type T aus einem existierenden T rauskopieren darf und, sobald ich sie wieder in ein T hineinkopiere, dieses dann das Value des ursprünglichen T hat...
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: (erledigt)[C++]struct auf zu kleinem Puffer standardkonf

Beitrag von Krishty »

Das S wurde in der Tat dort konstruiert, nur eben nicht während der Laufzeit des Prozesses ;)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: (erledigt)[C++]struct auf zu kleinem Puffer standardkonf

Beitrag von dot »

Krishty hat geschrieben:Das S wurde in der Tat dort konstruiert, nur eben nicht während der Laufzeit des Prozesses ;)
Jo, das ist mir schon klar, auch dass das alles so funktionieren wird. Nur nach meiner Leseart des Standard müsste das streng genommen dennoch UB sein und man kann leider nicht wirklich was dran ändern (malloc() + Cast müsste imo btw auch UB sein)... ;)
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: (erledigt)[C++]struct auf zu kleinem Puffer standardkonf

Beitrag von Krishty »

malloc() & Cast dürfte sogar recht sicher UB sein, so lange man kein Placement new drauf durchführt. In C hingegen ist das (natürlich) völlig legal, also benenne ich im Zweifel die Quelldatei der Parsing-Funktion in foo.c um :)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: (erledigt)[C++]struct auf zu kleinem Puffer standardkonf

Beitrag von dot »

Krishty hat geschrieben:[...]also benenne ich im Zweifel die Quelldatei der Parsing-Funktion in foo.c um :)
Das sollte in der Tat eine gültige Lösung sein...xD
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: (erledigt)[C++]struct auf zu kleinem Puffer standardkonf

Beitrag von CodingCat »

Krishty hat geschrieben:malloc() & Cast dürfte sogar recht sicher UB sein, so lange man kein Placement new drauf durchführt. In C hingegen ist das (natürlich) völlig legal, also benenne ich im Zweifel die Quelldatei der Parsing-Funktion in foo.c um :)
Nah, (§3.8) Object Lifetime beginnt wenn Speicherplatz mit korrektem Alignment für Typ T da ist, und [Initialisierung vollständig durchgeführt ODER trivial] ist. malloc kümmert sich für alle fundamentalen Typen T und Aggretates davon um korrektes Alignment, ergo funktioniert das mit dem Cast schon und der dynamische Typ T des Objekts steht fest, sobald Speicher durch den Cast mit diesem in Verbindung gebracht wird. Für nicht-triviale Typen muss der Cast logischerweise durch ein Placement-New zur vollständigen Initialisierung/Konstruktion ersetzt werden.

Das beantwortet auch die Eingangsfrage, sofern der Speicher aus deiner Datei das richtige Alignment UND die richtige Größe hat, ist alles in Ordnung. Letzteres ist offensichtlich manchmal verletzt. Der Compiler könnte z.B. beide Loads zusammenfassen und aus dem if ziehen, sobald nach dem ersten Zugriff klar ist, dass ein gültiges Objekt vorliegen muss; was er vermutlich nie tun wird, aber darum geht es bei der Frage nach undefiniertem Verhalten ja nicht. Das mit dem memcpy ist übrigens auch unnötig, §3.10.10 erlaubt explizit auch Aliasing durch Elemente von glvalues (Zeiger/Referenzen) von Aggregates oder Unions. §9.2.19 garantiert außerdem, dass ein Zeiger auf ein struct-Objekt immer auch auf das erste Element zeigt. Insofern wäre es z.B. möglich, die verschiedenen Varianten mit geschachtelten structs umzusetzen, wobei die innerste Struktur den kleinstmöglichen Header darstellt, und dann von optional ausführlicheren Header-Strukturen als geschachteltes Element an erster Stelle eingebunden wird, SOFERN dem Compiler z.B. durch non-standard Extensions das richtige Padding der enthaltenen Strukturen aufgezwungen wird (soll heißen, das Padding mit dem die Datei erstellt wurde). Das Padding-Problem gilt natürlich generell für structs - nicht nur für die Endbytes von geschachtelten; auch zwischen den Elementen.

Fazit: Sobald Größe, Alignment, Padding und damit die Byte-Repräsentation stimmt, existieren nach §3.8 erstmal alle potentiellen trivialen Objekte, die dort hinein passen, insbesondere auch das größtmögliche aggregierte.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: (erledigt)[C++]struct auf zu kleinem Puffer standardkonf

Beitrag von dot »

Ah, hab §3.8 übersehen, das ist natürlich nice, damit ist die Sache wohldefiniert; thx für den Hinweis, jetzt kann ich gleich viel besser schlafen... :D
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: (erledigt)[C++]struct auf zu kleinem Puffer standardkonf

Beitrag von Krishty »

Napf Brekkies für meine Lieblingskatze

Bild

dot, erledigt das auch, womit wir im letzten Thread aneinandergeraten sind?
dot hat geschrieben:
Krishty hat geschrieben:Und ein Haufen von nicht-konstruierten Objekten macht halt *doch* Sinn, wenn es sich um POD handelt :)
Nö, ein Haufen von nicht-konstruierten POD Objekten macht genauso keinen Sinn, denn nicht-konstruierte Objekte existieren per Definition nicht, egal wie P oder O der T auch noch sein mag... :P
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Antworten