size_t, ptrdiff_t and std::vector::size()

7

我认为用于存储指针之间差异的正确类型是ptrdiff_t

因此,我对我的STL(msvc 2010)实现其std::vector::size()函数的方式感到困惑。返回类型为size_t(据我所理解,这是标准规定的),但它是通过指针之间的差异计算出来的:

// _Mylast, _Myfirst are of type pointer
// size_type, pointer are inherited from allocator<_Ty>
size_type size() const 
{
    return (this->_Mylast - this->_Myfirst);
}

显然,为了确定size_typepointer的确切类型,需要进行一些元魔法。为了“确保”它们的类型,我检查了以下内容:

bool bs = std::is_same<size_t, std::vector<int>::size_type>::value;
bool bp = std::is_same<int * , std::vector<int>::pointer>::value;
// both bs and bp evaluate as true, therefore:
//   size_type is just size_t
//   pointer is just int*

使用/Wall编译以下内容,会出现signed-to-unsigned mismatch的警告,但是对于mysize1没有任何警告:
std::vector<int> myvector(100);
int *tail = &myvector[99];
int *head = &myvector[ 0];
size_t mysize1 = myvector.size();
size_t mysize2 = (tail - head + 1);

mysize2的类型更改为ptrdiff_t不会产生警告。 将mysize1的类型更改为ptrdiff_t会产生一个unsigned-to-signed mismatch警告。

显然我漏掉了什么...

编辑:我不是在问如何通过强制转换或#pragma disable(xxx)来抑制警告。我关心的问题是size_tptrdiff_t可能具有不同的允许范围(在我的机器上确实如此)。

考虑std::vector<char>::max_size()。我的实现返回一个等于std::numeric_limits<size_t>::max()max_size。由于vector::size()在强制转换为size_t之前创建了一个ptrdiff_t类型的中间值,因此似乎可能存在问题- ptrdiff_t不足以容纳vector<char>::max_size()


(size_t) (tail - head + 1); 修复警告?难道不是吗? - user90150
@iammilind,GS:谢谢,但我不需要转换 - 请看我的编辑。 - Darren Engwirda
2个回答

6
通常来说,ptrdiff_t是一个带符号整数类型,大小与size_t相同。它必须带有符号,以便表示p1 - p2p2 - p1
在std::vector的内部实现中,实现者有效地从end() - begin()导出了size()。由于std::vector的保证(连续的基于数组的存储),结束指针的值将始终大于开始指针的值,因此不会有生成负值的风险。事实上,size_t将始终能够表示比ptrdiff_t更大的正范围,因为它不必使用其一半的范围来表示负值。实际上,这意味着在这种情况下从ptrdiff_t到size_t的转换是一种扩展转换,具有明确定义的(并且直观明显的)结果。
同时,请注意这不是std::vector的唯一可能的实现。它可以被实现为单指针和一个持有大小的size_t值,通过begin() + size()导出end()。该实现也可以解决您对max_size()的担心。实际上,max_size从未真正可达——它需要为向量的缓冲区分配整个程序地址空间,不留给begin()/end()指针,函数调用堆栈等。

基本上同意,但是在 vector<char> 的情况下,我认为一旦向量 >= 2GBptrdiff_t 就会溢出,这并不是整个地址空间(在我的32位机器上),所以你实际上可能会在这里遇到问题,对吧??完全同意您讨论的“其他”向量实现在这种情况下将是健壮的。 - Darren Engwirda
我认为这在实践中不是问题,因为有符号/无符号转换的语义在这种情况下是明确定义且保留值。在这些操作中,你不会在C++中得到任何运行时异常,所以只剩下编译时警告,在这种情况下MS已经使用#pragma取消了警告。 - Drew Hall
3
@Drew:确实,负数在转换为“无符号数”时有一个明确定义的方式,但如果减法运算本身导致有符号整数发生了溢出,我不认为规范保证其结果。然而,任何库的具体实现都可以依赖于编译器的实现细节,因为它们是打包在一起的。因此,如果这个编译器确保模2运算,那么实现就是好的;这就是要点。 - Nemo
1
@Drew,Nemo:如果你对两个指针进行差运算,中间结果总是ptrdiff_t。如果分配的内存足够大,减法可能会溢出,并且(假设使用2的补码整数行为)你最终会得到一个负的中间结果。此时强制转换为size_t是“值保留”的,最终你将得到正确的正size_t结果。但这完全取决于2的补码ptrdiff_t溢出行为。我理解得对吗? - Darren Engwirda
2
std::vector 代码是实现的一部分,可能依赖于实现的其他部分。因此,MSVC 向量可以使用特定于 MSVC 的转换。 - MSalters

0

STL 中 std::vector::size() 的实现没有问题。

this->_Mylast - this->_Myfirst == vector size 只是一个巧合,它依赖于向量的实现方式。

此外,MSVC STL 向量实现有一个 #pragma warning(disable: 4244),可以消除警告。


我认为关闭警告并不能解决潜在的问题。size_tptrdiff_t可能具有不同的允许范围(至少在我的32位机器上是这样),其中max(ptrdiff_t)max(size_t)的一半。 - Darren Engwirda
1
@Darren 由于代码是由实现者编写的,因此他们完全可以依赖隐式转换。 - Luc Danton
根据我的编辑,似乎 ptrdiff_t 不足以容纳 vector<char>::max_size。仅依靠静默转换是可行的吗?这里不会有溢出问题吗? - Darren Engwirda
@Darren 因为他们是实现者。他们比 C++ 规范更了解——因为他们正在实现它。 - Luc Danton

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