26

我在使用 C++ 方面还很陌生,还没有掌握该语言的所有复杂性和微妙之处。

在 C++11 中向任何类型的指针添加任意字节偏移量的最便携、正确和安全的方法是什么?

SomeType* ptr;
int offset = 12345 /* bytes */;
ptr = ptr + offset;             // <--

我在 Stack Overflow 和 Google 上找到了很多答案,但它们都提出了不同的建议。我遇到的一些变种:

  1. 投射到char *

    ptr = (SomeType*)(((char*)ptr) + offset);
    
  2. 投射到unsigned int

    ptr = (SomeType*)((unsigned int)ptr) + offset);
    
  3. 投射到size_t

    ptr = (SomeType*)((size_t)ptr) + offset);
    
  4. size_t并且ptrdiff_t总是与指针的大小一致。因此,正是这些类型应该用作大型数组的索引,用于存储指针和指针运算。” -关于CodeProject 上的size_t 和 ptrdiff_t

    ptr = (SomeType*)((size_t)ptr + (ptrdiff_t)offset);
    
  5. 或者像以前一样,但使用intptr_t而不是size_t,它是有符号的而不是无符号的:

    ptr = (SomeType*)((intptr_t)ptr + (ptrdiff_t)offset);
    
  6. 只转换为intptr_t, 因为offset已经是一个有符号整数而intptr_t不是size_t

    ptr = (SomeType*)((intptr_t)ptr) + offset);
    

在所有这些情况下,使用旧的 C 风格转换是否安全,或者使用static_castreinterpret_cast为此更安全或更便携?

我应该假设指针值本身是无符号的还是有符号的?

4

5 回答 5

12

我会使用类似的东西:

unsigned char* bytePtr = reinterpret_cast<unsigned char*>(ptr);
bytePtr += offset;
于 2013-04-10T18:58:16.437 回答
12

使用reinterpret_cast(或 C 风格的转换)意味着绕过类型系统并且不可移植且不安全。是否正确,取决于您的架构。如果您(必须)这样做,则暗示您知道自己在做什么,并且从那时起您基本上就靠自己了。警告就这么多。

如果将数字添加n到指针或类型,则按类型元素T移动此指针。您正在寻找的是一种类型,其中 1 个元素表示 1 个字节。n T

sizeof第 5.3.3.1 节:

sizeof 运算符产生其操作数的对象表示中的字节数。[...] sizeof(char)sizeof(signed char)并且sizeof(unsigned char)1。sizeof 应用于任何其他基本类型(3.9.1)的结果是实现定义的。

请注意,没有关于sizeof(int)等的声明。

字节的定义(第 1.7.1 节):

C++ 内存模型中的基本存储单元是字节。一个字节至少大到足以包含基本执行字符集 (2.3) 的任何成员和 Unicode UTF-8 编码形式的八位代码单元,并且由连续的位序列组成,其数量为实现定义。[...] C++ 程序可用的内存由一个或多个连续字节序列组成。每个字节都有一个唯一的地址。

因此,如果sizeof返回字节数并且sizeof(char)为 1,char则 C++ 的大小为 1 个字节。因此,逻辑上char是C++ 的一个字节,但不一定是事实上的标准 8 位字节。添加到 a将返回一个字节(根据 C++ 内存模型)的指针。因此,如果您想玩按字节操作对象指针的危险游戏,您应该将其强制转换为其中一种变体。如果你的类型也有限定符,你也应该把它们转移到你的“字节类型”中。nchar*ncharconst

    template <typename Dst, typename Src>
    struct adopt_const {
        using type = typename std::conditional< std::is_const<Src>::value,
            typename std::add_const<Dst>::type, Dst>::type;
    };

    template <typename Dst, typename Src>
    struct adopt_volatile {
        using type = typename std::conditional< std::is_volatile<Src>::value,
            typename std::add_volatile<Dst>::type, Dst>::type;
    };

    template <typename Dst, typename Src>
    struct adopt_cv {
        using type = typename adopt_const<
            typename adopt_volatile<Dst, Src>::type, Src>::type;
    };

    template <typename T>
    T*  add_offset(T* p, std::ptrdiff_t delta) noexcept {
        using byte_type = typename adopt_cv<unsigned char, T>::type;
        return reinterpret_cast<T*>(reinterpret_cast<byte_type*>(p) + delta);
    }

例子

于 2013-04-11T08:13:08.630 回答
2

请注意,NULL是特殊的。在其上添加偏移量是危险的。
reinterpret_cast不能删除constvolatile限定词。更便携的方式是 C-style cast。
reinterpret_cast像@user2218982's answer这样的特征,似乎更安全。

template <typename T>
inline void addOffset( std::ptrdiff_t offset, T *&ptr ) { 
    if ( !ptr )
        return;
    ptr = (T*)( (unsigned char*)ptr + offset );
} 
于 2016-06-14T07:32:39.013 回答
0

我的没有那么优雅,但我希望更具可读性。char helper_ptr; helper_ptr= (char ) ptr;

然后你可以使用 helper_ptr 逐字节遍历。

ptr = (SomeType*)(((char*)ptr) + 1) 将 ptr 推进 sizeof(SomeType) 而不是 1 个字节。

于 2020-06-03T06:50:02.857 回答
-2

如果你有:

myType *ptr;

你也是:

ptr+=3;

编译器肯定会通过以下方式增加您的变量:

3*sizeof(myType)

据我所知,这是执行此操作的标准方法。

如果你想迭代让我们说一个 myType 类型的元素数组,那就是这样做的方法。

好的,如果你想使用

myNewType *newPtr=reinterpret_cast < myNewType * > ( ptr )

或者坚持使用普通的旧 C 并执行以下操作:

myNewType *newPtr=(myNewType *) ptr;

然后递增

于 2013-04-10T19:01:37.653 回答