像这样通过一个const指针来修改一个非const指针是否合法?

4
考虑以下函数:
void f(int const* p)
{
    *const_cast<int*>(id(p)) = 0;
}

这样做合法吗?假设f总是以int*作为参数传递?我不是在问这是否是一个好的做法,只是想要一个严格正式的答案。
让我有点担心的是,如果可以这样做,优化器将更难利用常量。考虑一个更复杂的例子:
// identity, always returns what it gets
uintptr_t id(uintptr_t p)
{
    static unsigned int const ar[5] {0x12345678, 0x87654321, 0x02468ACE, 0xECA86420, 0x88888888};

    for (size_t i = 0; i < 5; ++i)
        p ^= ar[2*i % 5];

    for (size_t i = 0; i < 5; ++i)
        p ^= ar[3*i % 5];

    return p;
}

void f(int const* p)
{
    uintptr_t q = id(reinterpret_cast<uintptr_t>(p));
    *reinterpret_cast<int*>(q) = 0;
}

这样做是否合法?如果不合法,将参数更改为int* p是否合法?

1
修改可修改的对象没有问题。无论你如何操作(因此使用“const_cast”原则上不是问题)。更让我担心的是‘id’函数。什么构成有效指针有一些规则,我无法立即看出‘id’是否保留了这种有效性。 - Kerrek SB
@Kerrek SB:id 总是返回它所得到的相同数字。(id 代表身份,也不要在意常量,它们可以是任何东西) - user2345215
@KerrekSB ^ 的结合性表明代码等价于 p ^= ar[0]^ar[2]^ar[4]^ar[1]^ar[3]; /* 第一次循环 */ p ^= ar[0]^ar[3]^ar[1]^ar[4]^ar[2]; /* 第二次循环 */,即 p ^= ar[0]^ar[2]^ar[4]^ar[1]^ar[3]^ar[0]^ar[3]^ar[1]^ar[4]^ar[2];^ 的交换律意味着后者等价于 p ^= ar[0]^ar[0]^ar[1]^ar[1]^ar[2]^ar[2]^ar[3]^ar[3]^ar[4]^ar[4];,即 p ^= 0^0^0^0^0;。由于对于所有的 xx ^ 0 = x,因此 p 不会改变。我猜测 OP 是在测试优化器是否能够找出并删除不必要的代码(只留下 return p; 语句)。 - Cassio Neri
@Cassio Neri:恰恰相反,我正在确保优化器不会删除它。此外,任何大于5的质数也可以,因为如果是质数,则索引不能重复,因此必须恰好运行所有索引一次(无论以什么顺序发布)。 - user2345215
@rozina: 因为我想让编译器感觉不知道结果是什么,所以它必须认为这是另一个变量。(当然你不会写出这样的代码,我只是想了解语言的限制) - user2345215
显示剩余6条评论
1个回答

6

如果指向的值实际上是非常量的,那么使用const_cast是合法的,这也是为什么我们首先有了const_cast。如果值实际上是常量并且您取消了const,则结果未定义。

如果f总是被赋予一个int*,那么为什么不声明f为非常量呢?或者你可以提供一个重载:

void f(int const* a)
void f(int *a)

为了让编译器调用正确的版本,并使用户了解您的意图。调用第一个版本并更改值可能会让调用f的任何人感到惊讶。


因为我想知道优化器的极限。那么 g 也可以吗? - user2345215
当你使用reinterpret_cast时,基本上是告诉编译器停止关注,因为你最清楚,所以是的!为什么要将其转换为uintptr_t,然后再转换为int* - Sean
@user2345215 - 是的,但在g中你没有使用异或运算。 - Sean
好的,既然我似乎没有得到其他意见,我想接受这个建议。但是你可能误读了g函数,它对参数应用了一堆xor(尽管最终保留了它)。我只是想知道你确定g也是合法的。 - user2345215
1
@Sean:强制转换掉 const 总是有效的。修改一个不可变对象(包括如果你首先强制转换掉了 const)是无效的。 - Lightness Races in Orbit
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接