将字节偏移量添加到任何指针的便携且安全的方法

时间:2013-04-10 18:55:15

标签: c++ pointers c++11 pointer-arithmetic

我在使用C ++方面很陌生,并没有掌握语言的所有错综复杂和细微之处。

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

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

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

  1. Cast to char *

    ptr = (SomeType*)(((char*)ptr) + offset);
    
  2. 转换为unsigned int

    ptr = (SomeType*)((unsigned int)ptr) + offset);
    
  3. Cast to size_t

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

    ptr = (SomeType*)((size_t)ptr + (ptrdiff_t)offset);
    
  5. 或者与之前一样,但使用intptr_t instead of size_t,签名而不是签名:

    ptr = (SomeType*)((intptr_t)ptr + (ptrdiff_t)offset);
    
  6. 仅转换为intptr_t,因为offset已经是有符号整数且intptr_t is not size_t

    ptr = (SomeType*)((intptr_t)ptr) + offset);
    
  7. 在所有这些情况下,使用旧的C风格演员表是否安全,或者为此使用static_castreinterpret_cast是否更安全或更便携?

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

5 个答案:

答案 0 :(得分:10)

我会使用类似的东西:

unsigned char* bytePtr = reinterpret_cast<unsigned char*>(ptr);
bytePtr += offset;

答案 1 :(得分:10)

使用reinterpret_cast(或C风格的演员表)意味着规避类型系统并且不便携且不安全。是否正确,取决于您的架构。 如果您(必须)这样做,那么您暗示您知道自己做了什么,从那时起您基本上就是自己。非常警告。

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

来自sizeof部分5.3.3.1。:

  

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

请注意,没有关于sizeof(int)等的陈述

byte 的定义(第1.7.1节):

  

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

因此,如果sizeof返回字节数且sizeof(char)为1,则char的大小为C ++的一个字节。因此,char 逻辑上一个字节到C ++,但不一定是事实上的标准8位字节。 将n添加到char*将返回指针n字节(就C ++内存模型而言)。因此,如果您想要以字节方式操作对象指针的危险游戏,则应将其强制转换为char变体之一。 如果您的类型也包含const等限定符,则应将它们转换为“字节类型”。

    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);
    }

Example

答案 2 :(得分:1)

请注意,NULL很特别。在其上添加偏移是危险的。
reinterpret_cast无法移除constvolatile限定符。更便携的方式是C风格的演员。
reinterpret_cast具有@ user2218982的答案等特征,似乎更安全。

template <typename T>
inline void addOffset( std::ptrdiff_t offset, T *&ptr ) { 
    if ( !ptr )
        return;
    ptr = (T*)( (unsigned char*)ptr + offset );
} 

答案 3 :(得分:0)

我的不是那么优雅,但我希望它更具可读性。 字符 helper_ptr; helper_ptr =(char )ptr;

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

ptr =(SomeType *)((((char *)ptr)+ 1)将使ptr增大sizeof(SomeType)而不是1个字节。

答案 4 :(得分:-2)

如果你有:

myType *ptr;

你做了:

ptr+=3;

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

3*sizeof(myType)

据我所知,这是标准的做法。

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

好的,如果你想使用

进行投射
myNewType *newPtr=reinterpret_cast < myNewType * > ( ptr )

或者坚持使用普通的旧C并做:

myNewType *newPtr=(myNewType *) ptr;

然后递增