문제

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:

  • Are such declarations or calls in context of const correctness allowed with regards to language constraints and/or undefined behavior?
  • Is it allowed to change one object, that is referenced protected and not protected in the same function?
  • I know there are some problems with accessing / modifying the same variable multiple times between, sequence points (although I am not perfectly sure if I understand the sequence point thing totally). Does this or similar issues apply here, and in which way?
  • If not undefined behavior, is there any other typical problem which reduces portability in the above situation?
  • If there are problems, what would be a good (safe and if possible with as little overhead as possible) general pattern to allow those calls, so that such issues might not happen every other line?

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.

  1. Is it allowed to change the pointer? Example: p=5;
  2. Is it allowed to change the object pointed to? Example: p->x = 5;

These are the four possibilities:

  • T* p: both changes allowed
  • const T* p: object can not be changed
  • T* const p: pointer can not be changed
  • const T* const p: neither object nor pointer can be changed

In your example void funct1(const tS * const i_pS, tS * const o_pS) this means the following:

  • You are not allowed to change the pointers i_pS and o_pS.
  • You are not allowed to change the object pointed to by i_pS.
  • You are allowed to change the object pointed to by 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.


다른 팁

This call is invalid. For example:

funct1(&my_struct.substruct, &my_struct.substruct);

because funct1 expects a tS * but these are ts *. You would need to get a cast to get this to compile. The code would then work (because there is actually a tS at the pointed-to location) but it is strange to say the least, you should just change it to &my_struct instead of adding the cast.

Also, I beg you to use a different naming convention than ts versus tS.

As Danvil says, it's important that your code takes account of the fact that the two pointers may be pointing to parts of the same object.

As a matter of style, I don't like the "top level const". It makes the function header harder to read, and you have to take some time to work out what is const and what isn't.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top