通过联合进行常量转换是否是未定义的行为?

发布于 2024-12-26 13:47:04 字数 547 浏览 1 评论 0原文

与 C++ 不同,C 没有 const_cast 的概念。也就是说,没有有效的方法将 const 限定指针转换为非限定指针:

void const * p;
void * q = p;    // not good

首先:此强制转换实际上是未定义的行为吗?

无论如何,海湾合作委员会对此发出警告。为了制作需要 const 强制转换的“干净”代码(即我可以保证不会改变内容,但我拥有的只是一个可变指针),我看到了以下“转换”技巧:

typedef union constcaster_
{
    void * mp;
    void const * cp;
} constcaster;

用法:<代码>u.cp = p; q = u.mp;。

通过这样的联合抛弃常量性的 C 语言规则是什么?我对 C 的了解非常零散,但我听说 C 对于联合访问比 C++ 宽松得多,所以虽然我对这种构造有不好的预感,但我想要一个来自标准的参数(我想是 C99,不过如果这在 C11 中发生了变化,那就很高兴知道)。

Unlike C++, C has no notion of a const_cast. That is, there is no valid way to convert a const-qualified pointer to an unqualified pointer:

void const * p;
void * q = p;    // not good

First off: Is this cast actually undefined behaviour?

In any event, GCC warns about this. To make "clean" code that requires a const-cast (i.e. where I can guarantee that I won't mutate the contents, but all I have is a mutable pointer), I have seen the following "conversion" trick:

typedef union constcaster_
{
    void * mp;
    void const * cp;
} constcaster;

Usage: u.cp = p; q = u.mp;.

What are the C language rules on casting away constness through such a union? My knowledge of C is only very patchy, but I've heard that C is far more lenient about union access than C++, so while I have a bad feeling about this construction, I would like an argument from the standard (C99 I suppose, though if this has changed in C11 it'll be good to know).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

别低头,皇冠会掉 2025-01-02 13:47:04

它的实现定义参见 C99 6.5.2.3/5:

如果使用联合对象的成员值时最多
最近存储到对象的是不同的成员,行为是
实现定义的。

更新:@AaronMcDaid 评论说这毕竟可能是明确定义的。

该标准规定了以下6.2.5/27:

类似地,指向兼容版本的合格或不合格版本的指针
类型应具有相同的表示和对齐方式
要求.27)

27) 相同的表示和对齐要求旨在
暗示作为函数参数的可互换性,返回值
职能和工会成员。

以及(6.7.2.1/14):

指向联合对象的指针,经过适当转换后,指向它的每个
成员(或者如果成员是位域,则转换为它所在的单元
驻留),反之亦然。

可能得出这样的结论:在这种特殊情况中,只有一种方法可以访问联合中的元素。

It's implementation defined, see C99 6.5.2.3/5:

if the value of a member of a union object is used when the most
recent store to the object was to a different member, the behavior is
implementation-defined.

Update: @AaronMcDaid commented that this might be well-defined after all.

The standard specified the following 6.2.5/27:

Similarly, pointers to qualified or unqualified versions of compatible
types shall have the same representation and alignment
requirements.27)

27) The same representation and alignment requirements are meant to
imply interchangeability as arguments to functions, return values from
functions, and members of unions.

And (6.7.2.1/14):

A pointer to a union object, suitably converted, points to each of its
members (or if a member is a bitfield, then to the unit in which it
resides), and vice versa.

One might conclude that, in this particular case, there is only room for exactly one way to access the elements in the union.

荒岛晴空 2025-01-02 13:47:04

我的理解是,只有当您尝试修改 const 声明的对象时,UB 才会出现。

所以下面的代码不是 UB:

int x = 0;
const int *cp = &x;
int *p = (int*)cp;
*p = 1; /* OK: x is not a const object */

但这是 UB:

const int cx = 0;
const int *cp = &cx;
int *p = (int*)cp;
*p = 1; /* UB: cx is const */

使用联合而不是强制转换在这里应该没有任何区别。

根据 C99 规范(6.7.3 类型限定符):

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

My understanding it that the UB can arise only if you try to modify a const-declared object.

So the following code is not UB:

int x = 0;
const int *cp = &x;
int *p = (int*)cp;
*p = 1; /* OK: x is not a const object */

But this is UB:

const int cx = 0;
const int *cp = &cx;
int *p = (int*)cp;
*p = 1; /* UB: cx is const */

The use of a union instead of a cast should not make any difference here.

From the C99 specs (6.7.3 Type qualifiers):

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.

草莓味的萝莉 2025-01-02 13:47:04

初始化肯定不会导致UB。 §6.3.2.3/2 (n1570 (C11)) 中明确允许限定指针类型之间的转换。之后对该指针中内容的使用会导致 UB(请参阅 @rodrigo 的答案)。

但是,您需要显式转换才能将 void* 转换为 const void*,因为简单赋值的约束仍然要求左侧的所有限定符都出现在右侧。

§6.7.9/11: ...对象的初始值是表达式的初始值(转换后);应用与简单赋值相同的类型约束和转换,将标量的类型视为其声明类型的非限定版本。

§6.5.16.1/1:(简单分配/约束)

  • ...两个操作数都是
    指向兼容类型的限定或非限定版本的指针,以及指向的类型
    左边的 to 拥有右边指向的类型的所有限定符;
  • ...一个操作数是一个指针
    一个对象类型,另一个是指向限定或非限定版本的指针
    void,并且左边指向的类型具有所指向类型的所有限定符
    从右边;

我不知道为什么 gcc 只是给出警告。


对于联合技巧,是的,它不是 UB,但结果仍然可能未指定。

§6.5.2.3/3 fn 95:如果用于读取联合对象内容的成员与最后用于在对象中存储值的成员不同,则适当的值的对象表示的一部分被重新解释为新类型中的对象表示,如 6.2.6 中所述(该过程有时称为“类型双关”)。这可能是一个陷阱表示。

§6.2.6.1/7:当值存储在联合类型对象的成员中时,对象表示的字节不对应于该成员,但对应于其他成员成员采用未指定的值。 (* 注:另请参阅 §6.5.2.3/6 了解例外情况,但此处不适用)


n1124(C99)中的相应部分为

  • C11 §6.3.2.3/2 = C99 §6.3.2.3/2
  • C11 § 6.7.9/11 = C99 §6.7.8/11
  • C11 §6.5.16.1/1 = C99 §6.5.16.1/1
  • C11 §6.5.2.3/3 fn 95 = 缺失(“类型双关语”未出现在 C99 中)
  • C11 §6.2.6.1/7 = C99 §6.2.6.1/7

The initialization certainly won't cause UB. The conversion between qualified pointer types is explicitly allowed in §6.3.2.3/2 (n1570 (C11)). It's the use of content in that pointer afterwards that cause UB (see @rodrigo's answer).

However, you need an explicit cast to convert a void* to a const void*, because the constraint of simple assignment still require all qualifier on the LHS appear on the RHS.

§6.7.9/11: ... The initial value of the object is that of the expression (after conversion); the same type constraints and conversions as for simple assignment apply, taking the type of the scalar to be the unqualified version of its declared type.

§6.5.16.1/1: (Simple Assignment / Contraints)

  • ... both operands are
    pointers to qualified or unqualified versions of compatible types, and the type pointed
    to by the left has all the qualifiers of the type pointed to by the right;
  • ... one operand is a pointer
    to an object type, and the other is a pointer to a qualified or unqualified version of
    void, and the type pointed to by the left has all the qualifiers of the type pointed to
    by the right;

I don't know why gcc just gives a warning though.


And for the union trick, yes it's not UB, but still the result is probably unspecified.

§6.5.2.3/3 fn 95: If the member used to read the contents of a union object is not the same as the member last used to store a value in the object, the appropriate part of the object representation of the value is reinterpreted as an object representation in the new type as described in 6.2.6 (a process sometimes called "type punning"). This might be a trap representation.

§6.2.6.1/7: When a value is stored in a member of an object of union type, the bytes of the object representation that do not correspond to that member but do correspond to other members take unspecified values. (* Note: see also §6.5.2.3/6 for an exception, but it doesn't apply here)


The corresponding sections in n1124 (C99) are

  • C11 §6.3.2.3/2 = C99 §6.3.2.3/2
  • C11 §6.7.9/11 = C99 §6.7.8/11
  • C11 §6.5.16.1/1 = C99 §6.5.16.1/1
  • C11 §6.5.2.3/3 fn 95 = missing ("type punning" doesn't appear in C99)
  • C11 §6.2.6.1/7 = C99 §6.2.6.1/7
泪痕残 2025-01-02 13:47:04

根本不要投射它。它是一个指向 const 的指针,这意味着不允许尝试修改数据,并且在许多实现中,如果指针指向不可修改的内存,将导致程序崩溃。即使您知道内存可以修改,也可能有其他指向它的指针不希望它发生更改,例如,如果它是逻辑上不可变字符串的存储的一部分。

该警告的存在是有充分理由的。

如果需要修改 const 指针的内容,可移植的安全方法是首先复制它指向的内存,然后对其进行修改。

Don't cast it at all. It's a pointer to const which means that attempting to modify the data is not allowed and in many implementations will cause the program to crash if the pointer points to unmodifiable memory. Even if you know the memmory can be modified, there may be other pointers to it that do not expect it to change e.g. if it is part of the storage of a logically immutable string.

The warning is there for good reason.

If you need to modify the content of a const pointer, the portable safe way to do it is first to copy the memory it points to and then modify that.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文