我在使用C ++方面很陌生,并没有掌握语言的所有错综复杂和细微之处。
在C ++ 11中向任何类型的指针添加任意字节偏移量的可移植,正确和安全的方法是什么?
SomeType* ptr;
int offset = 12345 /* bytes */;
ptr = ptr + offset; // <--
我在Stack Overflow和Google上找到了很多答案,但他们都提出了不同的建议。我遇到的一些变种:
ptr = (SomeType*)(((char*)ptr) + offset);
转换为unsigned int
:
ptr = (SomeType*)((unsigned int)ptr) + offset);
ptr = (SomeType*)((size_t)ptr) + offset);
“size_t
和ptrdiff_t
的大小总是与指针的大小一致。因此,这些类型应该用作大型数组的索引,用于存储指针和指针算术。“ - CodeProject上的About size_t and ptrdiff_t
ptr = (SomeType*)((size_t)ptr + (ptrdiff_t)offset);
或者与之前一样,但使用intptr_t
instead of size_t
,签名而不是签名:
ptr = (SomeType*)((intptr_t)ptr + (ptrdiff_t)offset);
仅转换为intptr_t
,因为offset
已经是有符号整数且intptr_t
is not size_t
:
ptr = (SomeType*)((intptr_t)ptr) + offset);
在所有这些情况下,使用旧的C风格演员表是否安全,或者为此使用static_cast
或reinterpret_cast
是否更安全或更便携?
我应该假设指针值本身是无符号还是有符号?
答案 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);
}
答案 2 :(得分:1)
请注意,NULL
很特别。在其上添加偏移是危险的。
reinterpret_cast
无法移除const
或volatile
限定符。更便携的方式是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;
然后递增