通过引用调用、const 正确性和对引用结构的读写访问 - 未定义的行为

标签 c undefined-behavior const-correctness c89 pass-by-reference

我有一堆代码,我应该对其进行分析并准备将其导入新项目。常有以下几种模式:

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

  1. 是否允许更改指针?示例:p=5;
  2. 是否允许更改指向的对象?示例: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_pSo_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)++; 如果 ab< 绝对是未定义的行为 指向同一个对象。


函数 void funct1(const tS* i_ps, tS* o_pS) 被调用为 funct1(&my_struct, &my_struct); 是一个容易混淆和错误的大门.

C 库也知道这个问题。考虑例如 memcpymemmove

因此,我建议构建您的函数,以确保不会发生未定义的行为。最严厉的措施是制作输入结构的完整副本。这有开销,但在您的特定情况下,也许只复制输入参数的一小部分就足够了。

如果开销太大,在函数文档中明确说明不允许给同一个对象作为输入和输出。然后,如果可能和必要,创建具有必要开销的第二个函数来处理输入和输出相同的情况。


关于通过引用调用、const 正确性和对引用结构的读写访问 - 未定义的行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23084752/

相关文章:

c++ - 指向常量值的常量数组指针

c - 使用函数 kill() 杀死具有相同父进程的进程

rust - 在 T 和 UnsafeCell<T> 之间转换是否安全且已定义行为?

c++ - 尝试 move 大型位集时 GCC4.6 出现段错误,这是编译器错误吗?

c - 为什么这个sscanf修改了一些我不想修改的数据?

C++ NULL 指针和 const 正确性

c - 比scanf快?

c - 使用C代码获取环境变量

c - if 语句计算流密码 Leviathan 源代码中的整数