I got a bunch of code, that I should analyze and prepare for import it to a new project. Often there are the following patterns:
typedef struct t_Substruct
{
/* some elements */
} ts;
typedef struct t_Superstruct
{
/* some elements */
ts substruct;
/* some more elements */
} tS;
void funct1(const tS * const i_pS, tS * const o_pS)
{ /* do some complicated calculations and transformations */ }
void funct2(const ts * const i_ps, tS * const o_pS)
{ /* do some complicated calculations and transformations */ }
void funct3(const tS * const i_ps, ts * const o_ps)
{ /* do some complicated calculations and transformations */ }
In general there is reading from the i_ parameters and writing to the o_ parameters. Now there might be calls like:
void some_funct()
{
tS my_struct = {0};
/* do some stuff */
funct1(&my_struct,&my_struct);
funct2(&my_struct.substruct, &my_struct);
funct3(&my_struct, &my_struct.substruct);
}
Im not sure about the possible pitfalls to such functions and calling context:
I have to implement in C90, but if there are issues in porting to other C incarnations, regarding the above, this is also important for me.
Thank you in advance.
There are two different sides related to const
with a pointer S* p
.
p=5;
p->x = 5;
These are the four possibilities:
T* p
: both changes allowedconst T* p
: object can not be changedT* const p
: pointer can not be changedconst T* const p
: neither object nor pointer can be changedIn your example void funct1(const tS * const i_pS, tS * const o_pS)
this means the following:
i_pS
and o_pS
.i_pS
.o_pS
.The first condition looks rather pointless, so probably this
void funct1(const tS* i_pS, tS* o_pS)
is more readable.
Regarding the second and third case where you have two pointers which point to the same part of an object: Be careful that you do not make wrong assumptions in the code, for example that the object pointed to by the const pointer is actually not changing.
Remember, a const pointer does never mean the object is not changing, only that you are not allowed to change it via that pointer.
Example for problematic code:
void foo(const S* a, S* b) {
if(a->x != 0) {
b->x = 0;
b->y = 5 / a->x; // why is a->x suddenly 0 ??
}
}
S s;
foo(&s, &s);
Regarding undefined behaviour and sequence points. I would advice to read this answer: Undefined behavior and sequence points
So for example the expression i = a->x + (b->x)++;
is definitely undefined behavior if a
and b
point to the same object.
A function void funct1(const tS* i_ps, tS* o_pS)
which is called as funct1(&my_struct, &my_struct);
is an open door to confusion and errors.
The C library also knows that problem. Consider for example memcpy
and memmove
.
So I would advice to build your functions such that you can be sure that no undefined behavior can occur. The most drastic measure would be to make a complete copy of the input struct. This has overhead, but in your specific case perhaps it is enough to copy only some small part of the input argument.
If the overhead is too big, specifically state in the function documentation that it is not allowed to give the same object as input and output. Then, if possible and necessary, create a second function with the necessary overhead to handle the case where input and output are the same.