通过union未定义的行为进行const-casting?
与C ++不同,C没有const_cast
概念。 也就是说,没有有效的方法将const限定指针转换为非限定指针:
void const * p; void * q = p; // not good
首先:这个演员实际上是未定义的行为吗?
无论如何,GCC警告这一点。 要制作需要const-cast的“干净”代码(即我可以保证我不会改变内容,但我所拥有的只是一个可变指针),我看到了以下“转换”技巧:
typedef union constcaster_ { void * mp; void const * cp; } constcaster;
用法: u.cp = p; q = u.mp;
u.cp = p; q = u.mp;
。
通过这种联盟抛弃常量的C语言规则是什么? 我对C的了解只是非常不完整,但我听说C对联合访问比C ++要宽松得多,所以虽然我对这个结构有一种不好的感觉,但我想从标准中得到一个论证(C99我想,虽然如果在C11中这已经改变了,那么知道它会很好。
它的实现已定义,参见C99 6.5.2.3/5:
如果在对象的最新存储是另一个成员时使用union对象的成员的值,则该行为是实现定义的。
更新: @AaronMcDaid评论说,毕竟这可能是明确定义的。
该标准规定了以下6.2.5 / 27:
类似地,指向兼容类型的限定或非限定版本的指针应具有相同的表示和对齐要求.27)
27)相同的表示和对齐要求意味着可互换性作为函数的参数,函数的返回值和联合的成员。
并且(6.7.2.1/14):
指向适当转换的联合对象的指针指向其每个成员(或者如果成员是位域,则指向它所在的单位),反之亦然。
有人可能会得出结论, 在这种特殊情况下 ,只有一种方法可以访问联合中的元素。
我理解只有当你试图修改一个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类型限定符):
如果尝试通过使用具有非const限定类型的左值来修改使用const限定类型定义的对象,则行为未定义。
初始化肯定不会导致UB。 在§6.3.2.3/ 2(n1570(C11))中明确允许限定指针类型之间的转换。 之后使用该指针中的内容会导致UB(请参阅@rodrigo的回答)。
但是,您需要使用显式转换将void*
转换为const void*
,因为简单赋值的约束仍然要求LHS上的所有限定符都出现在RHS上。
§6.7.9/ 11: ……对象的初始值是表达式的初始值(转换后); 与简单赋值相同的类型约束和转换适用,将标量的类型作为其声明类型的非限定版本。
§6.5.16.1/ 1 :(简单分配/ 约束 )
- …两个操作数都是兼容类型的限定或非限定版本的指针,左边指向的类型具有右边指向的所有类型的限定符;
- …一个操作数是指向对象类型的指针,另一个是指向
void
的限定或非限定版本的指针,左边指向的类型具有右边指向的所有类型的限定符;
我不知道为什么gcc只会发出警告。
对于联合技巧,是的,它不是UB,但结果可能仍未指定。
§6.5.2.3/ 3 fn 95 :如果用于读取union对象内容的成员与上次用于在对象中存储值的成员不同,则重新解释该值的对象表示的相应部分作为6.2.6中描述的新类型中的对象表示(有时称为“类型双关”的过程)。 这可能是陷阱表示。
§6.2.6.1/ 7 :当一个值存储在union类型的对象的成员中时,对象表示的不对应于该成员但对应于其他成员的字节采用未指定的值。 (*注意:有关例外情况,另见§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
根本不要施放它。 它是指向const的指针,这意味着不允许尝试修改数据,并且在许多实现中如果指针指向不可修改的内存,将导致程序崩溃。 即使您知道memmory可以被修改,也可能有其他指针,它不会期望它改变,例如,如果它是逻辑上不可变字符串的存储的一部分。
警告是有充分理由的。
如果你需要修改const指针的内容,那么便携式安全的方法是首先复制它指向的内存然后修改它。