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_3
and 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, ...);
}
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.
According to the C99 standard, two struct
s 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 bytp1
are compatible. Typet1
is also compatible with typestruct s1
, but not compatible with the typesstruct s2
,t2
, the type pointed to bytp2
, orint
.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With