首页 > 解决方案 > 使用char类型的glvalue访问另一种类型的对象时是否不需要导致UB

问题描述

int main(){
  int v = 1;
  char* ptr = reinterpret_cast<char*>(&v);
  char r = *ptr; //#1
}

在这个片段中,表达式 ptr 指向一个 int 类型的对象,根据:
expr.static.cast#13

否则,指针值不会因转换而改变。

根据expr.unary#op-1 ,间接ptr将产生一个表示对象ptr指向的左值

结果是一个左值,引用表达式指向的对象或函数。

根据basic.lval#11,使用允许类型的 glvalue 访问对象不会导致 UB

如果程序尝试通过类型与以下类型之一不相似 ([conv.qual]) 的泛左值访问 ([defns.access]) 对象的存储值,则行为未定义:

  • char、unsigned char 或 std​::​byte 类型。

它似乎也没有违反以下规则:
expr#pre-4

如果在计算表达式期间,结果未在数学上定义或不在其 type 的可表示值范围内,则行为未定义。

假设测试情况下的宽度char为8位,其范围为[-128, 127]。的v值为1。那么,这是否意味着片段#1不会导致 UB?

作为对比,给定以下示例

int main(){
  int v =  2147483647; // or any value greater than 127
  char* ptr = reinterpret_cast<char*>(&v);
  char r = *ptr; //#2
}

#2会是UB,对吧?

标签: c++language-lawyer

解决方案


语言的意图是两个片段都是实现定义的。我相信他们是,直到 C++17 中断了对该语言功能的支持。请参阅此处的缺陷报告。据我所知,这在 C++20 中还没有得到修复。

目前,访问内存表示的可移植解决方法是使用std::memcpy示例):

#include <cstring>

char foo(int v){
  return *reinterpret_cast<char*>(&v);
}

char bar(int v)
{
    char buffer[sizeof(v)];
    std::memcpy(buffer, &v, sizeof(v));
    return *buffer;
}

foo在技​​术上是 UB,而bar定义明确。原因是fooUB是遗漏的。标准未能定义的任何内容都是定义为 UB,并且该标准在其当前状态下无法定义此代码的行为。

bar生成与 gcc 10 相同的程序集foo。对于简单的情况,实际副本已被优化。

关于你的理性,推理似乎是合理的,除了我认为定义一元operator*expr.static.cast#13)的规则在这种情况下没有你期望的效果。指针必须指向底层表示,正如链接缺陷所描述的那样,它的定义很差。指针的值不变的事实并不能减轻它指向不同对象的事实。如果对象的类型不同,C++ 允许对象具有相同的地址,例如标准布局类中的第一个成员与拥有的实例共享相同的地址。

请注意,关于片段#1,作者是缺陷报告得出与您相同的结论,但我不同意。但由于我们正在处理一种语言缺陷,并且与国家意图相冲突,因此很难明确证明一种行为是正确的。众所周知,这些论点所依据的基本规则在这种特殊情况下存在缺陷。


推荐阅读