我有一堆代码,我应该对其进行分析并准备将其导入新项目。常有以下几种模式:
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 */ }
一般是从 i_ 参数读取和写入 o_ 参数。现在可能会有这样的调用:
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);
}
我不确定此类函数和调用上下文可能存在的缺陷:
- 对于语言约束和/或未定义的行为,是否允许在 const 正确性上下文中进行此类声明或调用?
- 是否允许更改在同一函数中引用 protected 和未 protected 对象?
- 我知道在序列点之间多次访问/修改同一个变量存在一些问题(尽管我不确定我是否完全理解序列点的事情)。这个问题或类似问题是否适用于此,以何种方式适用?
- 如果不是未定义的行为,在上述情况下是否还有其他典型问题会降低可移植性?
- 如果有问题,什么是允许这些调用的良好(安全且尽可能少的开销)通用模式,这样此类问题就不会每隔一行发生一次?
我必须在 C90 中实现,但如果在移植到其他 C 版本时出现问题,关于上述,这对我来说也很重要。
提前谢谢你。
最佳答案
const
有两个不同的方面,指针S* p
。
- 是否允许更改指针?示例:
p=5;
- 是否允许更改指向的对象?示例:
p->x = 5;
这是四种可能性:
T* p
:允许两种改变const T* p
: 对象不能改变T* const p
:指针不可改变const T* const p
:对象和指针都不能改变
在您的示例中 void funct1(const tS * const i_pS, tS * const o_pS)
这意味着以下内容:
- 不允许更改指针
i_pS
和o_pS
。 - 您不能更改
i_pS
指向的对象。 - 您可以更改
o_pS
指向的对象。
第一个条件看起来毫无意义,所以可能是这个
void funct1(const tS* i_pS, tS* o_pS)
更具可读性。
关于第二种和第三种情况,你有两个指针指向一个对象的同一部分:注意你不要在代码中做出错误的假设,例如const指针指向的对象实际上是不变。
请记住,const 指针绝不意味着对象不会更改,只是您不能通过该指针更改它。
有问题的代码示例:
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);
关于未定义的行为和序列点。我建议阅读这个答案:Undefined behavior and sequence points
例如表达式 i = a->x + (b->x)++;
如果 a
和 b< 绝对是未定义的行为
指向同一个对象。
函数 void funct1(const tS* i_ps, tS* o_pS)
被调用为 funct1(&my_struct, &my_struct);
是一个容易混淆和错误的大门.
C 库也知道这个问题。考虑例如 memcpy
和 memmove
。
因此,我建议构建您的函数,以确保不会发生未定义的行为。最严厉的措施是制作输入结构的完整副本。这有开销,但在您的特定情况下,也许只复制输入参数的一小部分就足够了。
如果开销太大,在函数文档中明确说明不允许给同一个对象作为输入和输出。然后,如果可能和必要,创建具有必要开销的第二个函数来处理输入和输出相同的情况。
关于通过引用调用、const 正确性和对引用结构的读写访问 - 未定义的行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23084752/