【问题标题】:How are constant struct members handled in C?C 中如何处理常量结构成员?
【发布时间】:2020-10-09 08:26:31
【问题描述】:

这样做可以吗?

struct MyStruct {
    int x;
    const char y; // notice the const
    unsigned short z;
};
struct MyStruct AStruct;
fread(&MyStruct, sizeof (MyStruct), 1,
      SomeFileThatWasDefinedEarlierButIsntIncludedInThisCodeSnippet);

我通过从文件写入整个结构来更改常量结构成员。这应该怎么处理?如果一个或多个结构成员是常量,这是写入非常量结构的未定义行为吗?如果是这样,处理常量结构成员的公认做法是什么?

【问题讨论】:

  • 你在编写什么样的软件?什么大小(数百万 C 源代码行)?什么市场?什么编译器?什么操作系统?
  • 请在您的问题中提供一些minimal reproducible example

标签: c struct constants undefined-behavior bus-error


【解决方案1】:

如何在 C 中处理常量结构成员?

阅读 C11 标准 n1570 及其与 const 限定符相关的 §6.7.3。

如果是这样,处理常量结构成员的公认做法是什么?

这取决于您是否更关心严格遵守 C 标准,还是更关心实际实现。请参阅 this draft 报告(2020 年 6 月正在进行的工作)讨论这些问题。这些考虑取决于分配给您的项目的开发工作,以及您的软件的portability(到其他平台)。

您可能不会在Covid respirator(或某些ICBM 内部)的嵌入式软件和Web 服务器(如lighttpdlibonion 等库上花费同样的精力或一些 FastCGI 应用程序)在廉价的消费设备中或在一些廉价租用的 Linux VPS 上运行。

考虑在您的代码中使用静态分析工具,例如 Frama-CClang static analyzer

关于未定义的行为,请务必阅读this blog

另请参阅this 对相关问题的回答。

我通过从文件写入整个结构来更改常量结构成员。

那么endianness 问题和file system 问题很重要。考虑也许使用与JSONYAML相关的库,或者混合使用sqlitePostGreSQLTokyoCabinet(以及所有这些open source库的源代码,或来自Linuxkernel,可能是鼓舞人心的)。

【讨论】:

    【解决方案2】:

    这是未定义的行为。

    C11 草案 n1570 说:

    6.7.3 类型限定符

    ...

    ...

    如果尝试通过使用具有非 const 限定类型的左值来修改使用 const 限定类型定义的对象,则行为未定义。

    我对此的解释是:为了符合标准,您只能在对象创建(也称为初始化)期间设置 const 成员的值,例如:

    struct MyStruct AStruct = {1, 'a', 2};  // Fine
    

    在做

    AStruct.y = 'b';   // Error
    

    应该给出一个编译器错误。

    您可以使用以下代码欺骗编译器:

    memcpy(&AStruct, &AnotherStruct, sizeof AStruct);
    

    它可能在大多数系统上都能正常工作,但根据 C11 标准,它仍然是未定义的行为。

    另见memcpy with destination pointer to const data

    【讨论】:

    • 如果AStruct是一个自动持续时间的对象,它的生命周期会在初始化之前开始,因此初始化会在它的生命周期内改变对象的值。另一方面,标准允许实现在超出标准规定的情况下有用地定义构造,作者认为没有必要列出所有否则会被认为是迟钝和无用的情况。
    • @supercat 嗯...也许...但是在标准中您在哪里看到对象的生命周期在初始化之前开始?我的解释是,生命周期一旦被初始化就开始了。
    • N1570 6.2.4 第 6 段:“对于这样一个没有可变长度数组类型的对象 [一个自动存储持续时间],它的生命周期从进入到块它是关联的,直到该块的执行以任何方式结束。”如果初始化中任何值的计算具有可观察到的副作用,则在执行进入块之前不会发生这种副作用,这意味着对象的生命周期已经开始。
    • 请注意,可能会执行类似struct foo {struct foo *next, ...whatever...} myFoo = {&myFoo}; 的操作,这意味着foo 在初始化完成之前会收到一个地址。
    • @supercat 我认为这是一个需要解释的极端案例。我的解释是,一旦初始化完成,对象的生命周期就开始了。
    【解决方案3】:

    该标准在定义和使用“对象”一词时有点草率。对于像“所有 X 必须是 Y”或“没有 X 可能是 Z”这样的陈述是有意义的,X 的定义必须具有不仅满足所有 X 的标准,而且明确排除所有不满足的对象必须为 Y 或允许为 Z。

    然而,“对象”的定义只是“执行环境中的数据存储区域,其内容可以表示值”。然而,这样的定义未能明确是否每个可能的连续地址范围总是一个“对象”,或者各种可能的地址范围何时受制于适用于“对象”的约束,何时不是。

    为了使标准能够明确地将极端情况分类为已定义或未定义,委员会必须就是否应定义或未定义达成共识。如果委员会成员根本不同意某些案件是否应该定义或未定义,那么以协商一致方式通过规则的唯一方法是,如果规则以一种模棱两可的方式编写,允许人们对应该定义的内容持不同意见的人认为该规则支持他们的观点。虽然我不认为委员会成员明确想让他们的规则模棱两可,但我认为委员会不可能就没有的规则达成共识。

    鉴于这种情况,许多操作,包括更新具有常量成员的结构,很可能属于标准不要求实现有意义地处理,但标准的作者会期望实现的操作领域无论如何都会有意义地处理。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-03-15
      • 1970-01-01
      • 2018-04-27
      • 2018-07-03
      • 1970-01-01
      • 1970-01-01
      • 2014-05-22
      • 1970-01-01
      相关资源
      最近更新 更多