Results 1 to 4 of 4

Pointerarithmetik in C und cast

  1. #1 Reply With Quote
    Veteran Feuerstern's Avatar
    Join Date
    Sep 2007
    Posts
    613
    Hallo zusammen,
    ich habe Uni bedingt C Code mit folgendem Ausschnitt:

    Code:
    typedef struct mem_node {
    	bool allocated;
    	int size;
    	struct mem_node *next;
    } mem_node_t;
    Code:
    mem_node_t* found_node = node;
    ...
    int mindestgroesse = size + sizeof(mem_node_t);
    // Wegen der Pointerarithmetik ist hier ein Cast (*char) erforderlich
    mem_node_t* zweiterTeil = (mem_node_t*) ((char*) found_node + mindestgroesse);
    Dort steht "Wegen der Pointerarithmetik ist hier ein Cast (*char) erforderlich".
    Aber wieso ist das so?
    Feuerstern is offline

  2. #2 Reply With Quote

    Batmanistrator
    Thoronador's Avatar
    Join Date
    Jul 2005
    Location
    Morrowind, Vvardenfell-Distrikt
    Posts
    18,875
    Um das zu verstehen, muss man die Eigenheiten der Pointerarithmetik verstehen. Pointerarithmetik in C funktioniert so, dass für einen Pointer ptr der Ausdruck ptr + 1 nicht unbedingt auf die Adresse zeigt, die ein Byte hinter der Adresse, auf die ptr zeigt, liegt. Stattdessen hängt die Adresse von der Größe des Pointertyps ab.

    Beispiel:

    Code:
    #include <stdio.h>
    #include <stdint.h>
     
    int main(void)
    {
        int64_t i64 = 9876;
        int64_t* i64_ptr = &i64;
        int64_t* i64_ptr2 = i64_ptr + 1;
        
        printf("size of int64_t: %li\n", sizeof(int64_t));
        printf("%li\n", i64_ptr);
        printf("%li\n", i64_ptr2);
        
        return 0;
    }
    Der Typ int64_t ist ein vorzeichenloser, 64-Bit großer Integertyp, d.h. er ist 8 Bytes groß. Entsprechend heißt das "+1" in der dritten Zeile innerhalb der main-Funktion auch, dass die neue Adresse so berechnet wird, dass eine Instanz von int64_t zwischen der ursprünglichen Adresse i64_ptr und i64_ptr2 Platz hat. Eine mögliche Ausgabe des obigen Programmcodes wäre folgende:

    Code:
    size of int64_t: 8
    140720987572248
    140720987572256
    Man sieht also, dass die Adressen sich nicht nur um ein Byte unterscheiden, sondern um acht Bytes, weil der Typ int64_t acht Bytes belegt.
    Der Typ char ist hingegen nur ein Byte groß:
    Code:
    #include <stdio.h>
    #include <stdint.h>
     
    int main(void)
    {
        char c = 'A';
        char* c_ptr = &c;
        char* c_ptr2 = c_ptr + 1;
        
        printf("size of char: %li\n", sizeof(char));
        printf("%li\n", c_ptr);
        printf("%li\n", c_ptr2);
        
        return 0;
    }
    Eine mögliche Ausgabe dieses Programmcodes ist:
    Code:
    size of char: 1
    140726725225831
    140726725225832
    Durch den Cast nach char wird also erreicht, dass der Plus-Operator bei der Adressberechnung die berechnete nur um mindestgroesse Bytes "verschiebt", und nicht um mindestgroesse * sizeof(mem_node_t) Bytes. Das liegt daran, dass mindestgroesse eine Größe in Bytes bezeichnen soll. Ich vermute zumindest anhand des Codeausschnitts, dass eine Speichergröße in Bytes gemeint ist.
    Thoronador is offline

  3. #3 Reply With Quote
    Veteran Feuerstern's Avatar
    Join Date
    Sep 2007
    Posts
    613
    Danke für deine verständliche und ausführliche Erklärung. Der Grund ist ja doch nachvollziehbarer als ich erwartet hätte
    Feuerstern is offline

  4. #4 Reply With Quote
    Abenteurer Feyn's Avatar
    Join Date
    Dec 2016
    Posts
    68
    Hallo

    Quote Originally Posted by Feuerstern View Post
    Code:
    typedef struct mem_node {
        bool allocated;
        int size;
        struct mem_node *next;
    } mem_node_t;
    Noch als kleine Ergänzung: Vorsicht bei zusammengesetzten Typen!
    sizeof(mem_node) gibt hier möglicherweise nicht das zurück, was man vielleicht erwarten würde (die Summe der Größe der enthaltenen Komponenten), sondern einen größeren Wert. Grund für dieses Verhalten ist "Alignment" von Datenstrukturen, welches darüber bestimmt, wie ein Datentyp gegenüber den Speicheradressen ausgerichtet ist.

    Ein pointer (x64) mit sizeof(pointer) == 8 muss typischerweise auch an einer Adresse liegen, die durch 8 teilbar ist. Durch Packing-Direktiven des jeweiligen Compilers kann dies jedoch auch geändert werden.

    Beispiel:

    Annahmen: x64 Target,
    sizeof(bool) == 1, Alignment 1
    sizeof(int) == 4, Alignment 4
    sizeof(struct mem_node*) == 8, Alignment 8
    default packing, keine speziellen Compiler-Anweisungen
    Code:
    typedef struct mem_node {
        bool allocated;
        int size;
        struct mem_node *next;
    } mem_node_t;
    
    // Die Komponente mit dem größten Alignment-Wert ist <next> mit Alignment 8, daher erhält struct mem_node ebenfalls einen Alignment-Wert von 8.
    
    
    // Der Datentyp beginnt also an einer durch 8 teilbaren Adresse
    typedef struct mem_node {
        
        bool allocated; // Alignment 1
    
        // Weil die nächste Komponente an einer durch 4 teilbaren Adresse liegen muss, fügt der Compiler zwischen <allocated> und <size> unsichtbare Bytes als Füllmaterial ein
    
        // 3 byte unnamed padding
      
        int size; // Alignment 4
    
        // Die vorherigen Komponenten haben nun eine Größe von 8, dadurch ist <next> bereits passend ausgerichtet
    
        struct mem_node *next;
    } mem_node_t;
    sizeof(struct mem_node) ist in diesem Fall also 16.
    Jetzt ein Beispiel mit anderer Reihenfolge:

    Code:
    typedef struct mem_node {
        bool allocated;
        struct mem_node *next;
        int size;
    } mem_node_t;
    
    
    typedef struct mem_node {
        bool allocated;
        // 7 byte unnamed padding
        struct mem_node *next;
        int size;
        // sizeof(struct mem_node) muss durch den Alignment-Wert teilbar sein, sonst ist der Datentyp in einem Array nicht mehr passend ausgerichtet
        // 4 byte unnamed padding
    } mem_node_t;
    sizeof(struct mem_node) ist in diesem Fall allso 24.
    Lange Rede, kurzer Sinn:
    In C und C++ hat die Reihenfolge der Komponenten innerhalb eines zusammengesetzten Types Auswirkung auf die Größe des Types, weil der Compiler die Reihenfolge nicht ändern darf. Zudem sind Größe und Alignment compiler- und oder plattformspezifisch. Es schadet nicht, dieses Verhalten im Hinterkopf zu behalten, wenn man Pointerarithmetik und Casts verwendet.
    Feyn is offline Last edited by Feyn; 29.12.2019 at 13:05.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •