将指向非限定类型的指针重新解释为指向限定类型的指针是否安全?考虑一个标准布局类型,其中包含指针成员“void * ptr”和另一种等效定义的标准布局类型,但带有“const void * ptr”。这些类型的布局是否兼容,这个答案是否取决于语言修订或C和C ++之间?
动机:
有时,当与C程序接口时,可以为某种类型的缓冲区定义结构分组参数。对于const-correctness,输入缓冲区应该有指向底层缓冲区的const指针,但输出缓冲区显然必须是可变的。
struct s1 { const void *ptr; }
struct s2 { void *ptr; }
const void *get_in_ptr(void);
void *get_out_ptr(void);
void alg(const s1 *in, const s2 *out);
void f()
{
s1 in_arg = { get_in_ptr() };
s2 out_arg1 = { get_out_ptr() };
s2 out_arg2 = { get_out_ptr() };
/* First algorithm pass. */
alg(&in_arg, &out_arg1);
/* Second algorithm pass. */
alg((const s1 *)&out_arg1, &out_arg2); /* Is this legal? */
}
请在任何答案中引用相关的标准出版物。
答案 0 :(得分:1)
C11 states:
对于任何限定符q,指向非q限定类型的指针可以转换为指向该类型的q限定版本的指针;存储在原始指针和转换指针中的值应相等。
因此,转换为合格版本是安全的,但反之亦然。丢弃 - 特别是const
或volatile可以使用不同的内存访问,例如对于哈佛架构,在程序存储器(PIC,AVR)中存储const
个变量。关于后果的标准非常clear:
如果尝试通过使用具有非const限定类型的左值来修改使用const限定类型定义的对象,则行为未定义。如果尝试通过使用具有非volatile限定类型的左值来引用使用volatile限定类型定义的对象,则行为是未定义的。
简单地说:一般来说,抛弃那些资格赛是不安全。但是,该标准没有提到对前const
个对象的读访问。
对于其他资格赛和更多细节,请自己阅读。
对于struct布局:C使用类型/布局的兼容性,而不是C ++。因此,如果它们使用相同的布局/标准类型,而不是相同的类型名称,则两种类型是兼容的。
答案 1 :(得分:-1)
[C99: 6.2.5/25, C11: 6.2.5/26]:
[..] 类型的限定或非限定版本是属于同一类型类别且具有相同表示和对齐要求的不同类型。 [..]
[C++03, C++11, C++14: 3.9.3/1]:
[..] 类型的cv限定版或cv非限定版是不同类型;但是,它们应具有相同的表示和对齐要求(3.11)。
对于封装类型:
[C99: 6.2.7/1]:
[..] 此外,如果标签和成员满足以下要求,则在单独的翻译单元中声明的两个结构,联合或枚举类型是兼容的:如果声明了一个使用标记,另一个应使用相同的标记声明。如果两者都是完整类型,则以下附加要求适用:其成员之间应存在一对一的对应关系,使得每对相应成员都声明为具有兼容类型,并且如果相应对的一个成员是使用名称声明,另一个成员使用相同的名称声明。 [..]
[C++03: 9.2/14]:
如果两个POD-struct(第9节)类型具有相同数量的非静态数据成员,则它们是布局兼容的,并且相应的非静态数据成员(按顺序)具有布局兼容类型(3.9)。
[C++11: 9.2/16, C++14: 9.2/17]:
两个标准布局结构(第9节)类型是布局兼容的,如果它们具有相同数量的非静态数据成员,并且相应的非静态数据成员(按声明顺序)具有布局兼容性类型(3.9)。
所以,假设我们忽略了类型系统绕过(const void*
→void*
到const_cast
直接或通过C风格转换 - 请注意reinterpret_cast
没有任何内容如何处理这个问题,明确禁止放弃 cv-qualifiers ),只讨论别名兼容性...
...是的,这是“安全的”。
这就是为什么我们能够巧妙地使用const_cast
,当古老的C API强迫我们接受他们的话时,而不是实际使用const
- 安全。
你知道这是可以的,因为当你写&in_arg
时,你的编译器隐含地将s1*
转换为const s1*
以进行调用,并且没有抱怨。