Pointer-Arithmertik



  • Hallo,
    ich arbeite mit void-Pointern. D. h., dass ich da nicht direkt zugreifen darf. Um aber dennoch damit arbeiten zu können, habe ich einfach die Adresse des Pointers gecastet. Ungefähr so:

    unsigned long pointerAddr = (unsigned long) void_pointer;
    

    Meine erste Frage: Sollte ich bei Adressen immer mit unsigned long arbeiten? Gibt es evtl. dafür einen passenden Typ dafür so etwas wie size_t oder ptrdiff_t?

    Zweite Frage:

    Eine Zuweisung geht am Besten über memcpy, oder?

    unsigned long effectiveAddr = pointerAddr + blub;
    
    memcpy(elemAddr, (void *) effectiveAddr, elemSize);
    

    Gibt's elegantere Möglichkeiten?

    Danke im Voraus!
    Steffo


  • Mod

    Ganz schlechte Idee. Du kannst nicht davon ausgehen, dass unsigned long mit einem Pointer kompatibel ist.

    Caste doch einfach in den Pointertyp den du haben möchtest, das ist das übliche Vorgehen. Hier zum Beispiel eine typische Vergleichsfunktion für qsort:

    int vergleiche_ints(const void *lhs, const void *rhs)
    {
     return  *(int*)lhs - *(int*)rhs;
    }
    

    P.S.: Ich sehe gerade, dass das sogar genau das Beispiel auf der verlinkten Seite ist 🙂 .



  • Mir ist jedoch absolut unbekannt, mit welchem Typ ich es zu tun habe, da das ein generischer Stack ist.
    Der Client kann alles möglich übergeben: jegliche Art von primitiven Datentypen und auch structs. Das einzige was mich dabei interessiert ist die Adresse und die Größe des Typs. Alles andere ist mir jedoch intransparent.

    Ich muss also mit void-Pointern arbeiten, ohne zu wissen, welcher Typ dahinter steckt. Unsigned long ist doch in ANSI-C der größte Datentyp. Kann ich denn nicht davon ausgehen, dass jede Adresse da rein passt?

    L. G.
    Steffo


  • Mod

    Warum arbeitest du dann nicht mit den void* weiter? Welchen Vorteil versprichst du dir von der Konvertierung in unsigned long? Und nein, du kannst nicht davon ausgehen, dass ein Pointer in irgendeiner Hinsicht mit unsigned long kompatibel ist.

    edit: Ich schieße mal ins Blaue, was du vielleicht nicht weißt: In C darfst du (im Gegensatz zu C++) mit void-Zeigern rechnen. Die Zeigerarithmetik entsprecht dabei dem Verhalten von char-Zeigern.
    edit2: Nein, das war doof missverständlich erklärt. Bessere Erklärung: void* ist garantiert kompatibel zu char* und du kannst dann in char* casten (oder meinetwegen gleich als char* speichern) und dann damit rechnen.



  • Tatsächlich haben Kernighan und Ritchie geschrieben:

    "Pointers and integers are not interchangeable. Zero is the sole exception"

    Hast also Recht.

    Allerdings hätte ich doch auch in einem x-beliebigen Typ casten können, weil das ja vollkommen legal in C ist, oder nicht?

    L. G.
    Steffo



  • Casten kannst du schon. Nur können dabei Daten verloren gehen.

    Wenn du nach char castest siehst du das sicher ein. Also nimm einen Pointertyp void* oder char*



  • In C99 gibt es (u)intptr_t. Das könnte das sein was du suchst.



  • DirkB schrieb:

    Casten kannst du schon. Nur können dabei Daten verloren gehen.

    Wenn du nach char castest siehst du das sicher ein. Also nimm einen Pointertyp void* oder char*

    Klar, mit char kann ich nur auf das erste Byte zugreifen, aber char*, int*, float* würden ja alle auf die gleiche Adresse zeigen und wenn ich damit dann Pointer-Arithmetik mache und die neu berechnete Adresse memcpy übergebe, dürfte doch dasselbe herauskommen, oder nicht?

    Versteht mich nicht falsch: Mir geht's um den technische Hintergrund. Einerseits möchte ich schon gerne wissen, weshalb ich unbedingt char* und nichts anderes verwenden sollte, andererseits muss ich auch einen Bericht schreiben und da sollte ich schon argumentieren, weshalb ich char* und nicht short* verwendet habe.

    @EinGast: Danke, für den Hinweis. Ich glaube aber, dass ich mich auf C89/90 beschränken soll - leider.

    L. G.
    Steffo


  • Mod

    Der technische Hintergrund ist Alignment. ints und floats sind gerne mal an bestimmten Adressen (i.d.R. durch 4 teilbar) zwangsausgerichtet. Wenn du jetzt einem int-Zeiger den Wert eines char- oder void-Zeigers (die i.d.R. keine feste Ausrichtung haben), dann passiert - ehrlich gesagt habe ich keine Ahnung, was dann passiert, aber es ist mit Sicherheit etwas Schlimmes.

    P.S.: Wenn du einen Bericht schreibst, dann argumentier doch einfach mit dem Standard. Der sagt klipp und klar
    Zeiger(alle) <-> Integertyp 👎
    void* <-> char* 👍
    Funktionszeiger <-> Datenzeiger 👎
    Datenzeiger -> void* 👍
    void* -> Datenzeiger 👍 , aber nur wenn's passt
    Zeigerarithmetik weiter als 1 über die Grenze eines Arrays 👎

    Und dafür gibt's auch jeweils technische Begründungen. Manche davon mögen veraltet sein (auf kaum einem System sind Funktionszeiger noch inkompatibel zu Datenzeigern), viele sind aber noch aktuell und garantiert ist dir sowieso nur das was im Standard steht.



  • Du hast mal wieder absolut recht.

    Hier mal ein paar Zitate aus dem Kapitel "3.3.4 Cast operators":

    "An arbitrary integer may be converted to a pointer. The result is implementation-defined."

    Von Integer nach Pointer zu casten ist implementierungsabhängig!

    "A pointer to an object or incomplete type may be converted to a pointer
    to a different object type or a different incomplete type. The
    resulting pointer might not be valid if it is improperly aligned for
    the type pointed to."

    Erfolgreiches Casten ist vom Data Alignment abhängig!

    "It is guaranteed, however, that a pointer to an object of a given alignment
    may be converted to a pointer to an object of the same alignment or a
    less strict alignment and back again; the result shall compare equal to the
    original pointer."

    Von int* nach short* ok, da short gleich bzw. weniger strikt beim Data Alignment ist.

    Daraus folgt:

    "An object that has character type has the least strict alignment."

    char* ist universell einsetzbar!

    Hast du dir den Standard als Bettlektüre durchgelesen?!

    EDIT:
    3.1.2.5 Types:

    "A pointer to void shall have the same representation and alignment
    requirements as a pointer to a character type."

    L. G.
    Steffo


  • Mod

    Steffo schrieb:

    Hast du dir den Standard als Bettlektüre durchgelesen?!

    Nicht ganz*. Dies ist bloß nicht das erste Mal, dass diese Frage kommt und ich bin gut mit Google, um Fakten die ich mal wusste schnell nochmal zu bestätigen 🙂 .

    *: Ein bisschen drin stöbern ist aber empfehlenswert, so dass man weiß, welche Information ungefähr wo zu suchen ist.



  • Steffo schrieb:

    Erfolgreiches Casten ist vom Data Alignment abhängig!

    Das ist doch Kokolores.
    Was gewinnt man denn, wenn man "erfolgreich" Zeiger auf unterschiedliche Typen gecastet hat? Nichts, nur Schrott, denn man verliert die wichtige Typinformation, d.h. man kann anschließend, nach dem "erfolgreichen" Cast, weder rumrechnen noch dereferenzieren. (ich habe mal gehört, dass das essentielle Anwendungen von Zeigern sind)
    Und char* als Ersatz für void* ist auch Schrott, da man hierbei die Compilerprüfungen "abschaltet", da man mit void* bekanntlich weder rumrechnen noch dereferenzieren kann und dabei sofort (zur Compilezeit) was auf die Finger bekommt, was wieder zum Thema Fehlerverschleierung führt.
    Ein char* statt eines void* suggeriert also nur falsche Sicherheit.



  • OK, jetzt hast du aber viel kritisiert. Was schlägst du stattdessen vor?!
    Ich schätze mal, du hast dir den Thread aufmerksam durchgelesen und kennst mein Problem in meinem Anwendungsfall...



  • SeppJ schrieb:

    Wenn du jetzt einem int-Zeiger den Wert eines char- oder void-Zeigers (die i.d.R. keine feste Ausrichtung haben), dann passiert - ehrlich gesagt habe ich keine Ahnung, was dann passiert, aber es ist mit Sicherheit etwas Schlimmes.

    Mal als Beispiel, wie das auf Maschinenebene aussehen könnte: auf einigen ARM-Prozessoren sind manche Register so verdrahtet, dass die niederwertigsten zwei Bits immer 0 sind. Ein falsches Alignment würde also die Folge haben, dass man vorher im Speicher liegende Objekte überschreibt.

    Natürlich unter der Annahme, dass der Compiler den kaputten Code 1:1 in den Maschinencode durchreicht und sich nicht doch lieber für Nasal Demons entscheidet.


Anmelden zum Antworten