Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Converting a pointer to a struct to its first member

Tags:

c

Consider the following example program:

#include <stdio.h>

struct base {
    int a, b;
};

struct embedded {
    struct base base;
    int c, d;
};

struct pointed {
    struct base* base;
    int c, d;
};

static void base_print(struct base* x) {
    printf("a: %d, b: %d\n", x->a, x->b);
}

static void tobase_embedded(void* object) {
    base_print(object); // no cast needed, suitably converted into first member.
}

static void tobase_pointed(void* object) {
    struct base* x = *(struct base**) object; // need this cast?
    base_print(x);
}

int main(void) {
    struct embedded em = {{4, 2}};
    struct pointed pt = {&em.base};
    tobase_embedded(&em);
    tobase_pointed(&pt);
    return 0;
}

Compiled with:

$ gcc -std=c99 -O2 -Wall -Werror -pedantic -o main main.c

The expected output is:

$ ./main
a: 4, b: 2
a: 4, b: 2

The C99 standard says this about the first member of a structure:

C99 6.7.2.1 (13): A pointer to a structure object, suitably converted, points to its initial member... and vice versa. There may be unnamed padding within as structure object, but not at its beginning.

In the example program a pointer to struct embedded is converted to a pointer to struct base (through void*) without the need for an explicit cast.

What if instead the first member is a pointer to base as in struct pointed? I'm unsure about the cast within tobase_pointed. Without the cast garbage is printed, but no compilation warnings/errors. With the cast the correct values for base.a and base.b are printed, but that doesn't really mean much if there is undefined behavior.

Is the cast to convert struct pointed into its first member struct base* correct?

like image 353
Adam Avatar asked Jan 22 '16 21:01

Adam


2 Answers

The code doesn't just casts, it also dereferences the pointer to the pointer to struct base. This is necessary to obtain the pointer to base in the first place.

This is what happens in your code, if the function tobase_pointed was removed:

struct pointed pt = {&em.base};
void* object = &pt;                  //pass to the function
struct base** bs = object;           //the cast in the function
assert( bs == (struct base**)&pt ) ; //bs points to the struct pointed
assert( bs == &(pt.base) ) ;         //bs also points to the initial member struct base* base
struct base* b = *bs ;               //the dereference in the function
base_print(x);

bs is the pointer that was suitably converted to point to the initial member. Your code is correct.

like image 116
2501 Avatar answered Sep 25 '22 23:09

2501


This cast is justified, and you need it because you want to convert a pointer into a pointer to pointer. If you do not cast, dereference will be incorrect.

In other words, your base* has the same address as pt object. So you can access it through a pointer to pt. But you have to dereference it.

like image 23
SergeyA Avatar answered Sep 24 '22 23:09

SergeyA