Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cast between struct pointer in C

Tags:

c

casting

Please consider the following code.

typedef struct{
    int field_1;
    int field_2;
    int field_3;
    int field_4;

    uint8_t* data;
    uint32_t data_size;
} my_struct;

void ext_function(inalterable_my_struct* ims, ...);

I want to allow ext_function (written by a third party) to modify only field_3and field_4 in my_struct. So I do the following:

typedef struct{
    const int field_1;
    const int field_2;
    int field_3;
    int field_4;

    const uint8_t* data;
    const uint32_t data_size;
} inalterable_my_struct;

void ext_function(inalterable_my_struct* ims, ...);

Is it safe to cast pointers between my_struct and inalterable_my_struct before calling ext_function (as shown after)?

void call_ext_function(my_struct* ms){
    inalterable_my_struct* ims = (inalterable_my_struct*)ms;
    ext_function(ims, ...);
}
like image 591
iome Avatar asked Dec 03 '12 14:12

iome


3 Answers

I don't think this is a good idea.

The called function can always cast away any const:ness, and modify the data if it wants to.

If you can control the callpoints, it would be better to create a copy and call the function with a pointer to the copy, then copy back the two fields you care about:

void call_ext_function(my_struct* ms)
{
    my_struct tmp = *ms;
    ext_function(&tmp, ...);
    ms->field_3 = tmp.field_3;
    ms->field_4 = tmp.field_4;
}

much cleaner, and unless you do this thousands of times a second the performance penalty should really be minor.

You might have to fake the pointer-based data too, if the function touches it.

like image 133
unwind Avatar answered Oct 17 '22 02:10

unwind


According to the C99 standard, two structs would not have compatible types even if their declarations were identical. From the section 6.7.7.5:

EXAMPLE 2 After the declarations

typedef struct s1 { int x; } t1, *tp1;
typedef struct s2 { int x; } t2, *tp2;

type t1 and the type pointed to by tp1 are compatible. Type t1 is also compatible with type struct s1, but not compatible with the types struct s2, t2, the type pointed to by tp2, or int.

Moreover, two types with different qualifiers are not considered compatible:

For two qualified types to be compatible, both shall have the identically qualified version of a compatible type; the order of type qualifiers within a list of specifiers or qualifiers does not affect the specified type.

A cleaner approach would be to hide your struct altogether, replace it with an obscure handle (a typedef on top of void*) and provide functions for manipulating the elements of the struct. This way you would retain full control over the structure of your struct: you would be able to rename its fields at will, change the layout as much and as often as you wish, change underlying types of the fields, and do other things that you normally avoid when the inner layout of the struct is known to your clients.

like image 35
Sergey Kalinichenko Avatar answered Oct 17 '22 04:10

Sergey Kalinichenko


I don't think it's a good idea, because it is hard to track whether the structure has been cast or not (especially if the code is large). Also casting it into const does not guarantee that it won't be cast to a non-const structure later.

The solution provided by unwind is a very good one. An alternate (and more obvious) solution would be to split the structure into two smaller parts.

typedef struct{
    const int field_1;
    const int field_2;
    const uint8_t* data;
    const uint32_t data_size;
} inalterable_my_struct;

typedef struct{
    int field_3;
    int field_4;
} my_struct;

void ext_function(const inalterable_my_struct* const ims, my_struct* ms ...);

I have made the pointer also constant in the above call, but that is not necessary.

like image 2
Desert Ice Avatar answered Oct 17 '22 02:10

Desert Ice