与 const 和非常量成员联合

Union with const and non-const members

我正在编写一些向用户公开 const 指针的库代码,但在某些操作期间我需要更改此指针指向的位置(幕后切换技巧)。我必须在不遇到 UB 或严格别名违规的情况下解决此问题的一个想法是使用带有 const 成员的联合:

// the pointed-to objects (in production code, these are actually malloc'd blocks of mem)
int x = 0, y = 7;

typedef union { int * const cp; int * p; } onion;
onion o = { .cp = &x };
printf("%d\n", *o.cp);   //  <---------------------- prints: 0
o.p = &y;
printf("%d\n", *o.cp);   //  <---------------------- prints: 7

但我不知道这是否定义明确...有人知道它是否(或不是)以及为什么吗?


编辑: 我想我是在混淆水域,因为很多人都要求澄清这方面的细节,而不是回答我想要的更简单的问题.

下面,我通过将类型从 int* 更改为 int 来简化代码,现在我的问题很简单:以下定义明确吗?

typedef union { int const cp; int p; } onion;
onion o = { .cp = 0 };
printf("%d\n", o.cp);   //  <---------------------- prints: 0
o.p = 7;
printf("%d\n", o.cp);   //  <---------------------- prints: 7

我读过的每一本编程书籍都告诉我以下内容。

static const int x = 7;
int *px = (int *)&x;

未定义,但

static int x = 7;
const int *px1 = &x;
int *px2 = (int *)px1;

已定义。也就是说,如果原始指针(此处为 &x)不是 const.

,则您始终可以丢弃 const-ness

在这里,我依靠的是没有来自任何质量来源的相反意见,也没有费心去查找标准(我不会为此付费)。

但是您正在尝试导出 const 不是 const 的内容。这实际上是有效的。该语言允许

extern const * int p;

在幕后可写。将其切换到定义未看到它 const 的文件的方法是将其定义为 int *p; 并小心不要将声明包含在包含定义的文件中。这使您可以不受惩罚地丢弃 const。写入它看起来像:

int x;

    *((int **)&p) = &x;

旧编译器过去常常拒绝 extern const volatile machine_register; 但现代编译器没问题。

如果接口是一个 const 声明的指针,例如 int *const(就像您在评论中指出的那样),那么您无法做任何更改都不会触发 UB .

如果您将 int * 存储在某处(例如,作为 static int *ip;)并通过 int *const* 指针公开其地址(例如,int *const* ipcp = &ip; ,然后您可以简单地重新转换回 (int**)(我给出的示例中 &ip 的原始类型)并使用它来访问 int* 指针。

我认为根据 C11 6.7.3 这是未定义的(等效段落在标准的所有版本中):

If an attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type, the behavior is undefined.

o.cp 无疑是一个用 const 限定类型定义的对象。

o.p 的修改在我看来确实算作修改 o.cp 的尝试,因为这正是我们这样做的原因!

该标准使用术语“对象”来指代许多概念,包括:

  1. 排他性静态、自动或线程持续时间的存储区域与“独立”命名标识符的关联,它将保存其除非使用从它派生的左值或指针进行修改,否则整个生命周期中的值。

  2. 由左值标识的任何存储区域。

在块范围内,声明 struct s1 { int x,y; } v1; 将导致创建一个名为 v1 的对象,该对象满足上面的第一个定义。在 v1 的生命周期内,不会有其他满足该定义的命名对象与同一存储关联。像 v1.x 这样的左值表达式将识别满足第二个定义的对象,而不是第一个,因为它会识别不仅与左值表达式 v1.x 相关联的存储,而且还与命名的标准相关联单独对象 v1.

我认为标准的作者没有充分考虑或就规则描述“对象”的含义的问题达成任何有意义的共识:

If an attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type, the behavior is undefined.

如果第一类对象是用 const 限定符定义的,那么试图修改它的代码的行为将超出标准的管辖范围,这当然是有道理的。如果将规则解释为更广泛地适用于其他类型的对象,那么在其生命周期内修改此类对象的操作也将超出标准的管辖范围,但标准实际上并没有有意义地描述第二个对象的生命周期类型为底层存储生命周期之外的任何类型。

将引用的文本解释为仅适用于第一类对象会产生清晰有用的语义;试图将其应用于其他类型的对象会产生更模糊的语义。也许这样的语义对某些目的有用,但与将文本应用于第一类对象相比,我看不出有任何优势。