64位系统中int和size_t的区别

17

将代码从32位移植到64位。有很多地方需要修改。

int len = strlen(pstr);

现在这些代码都会生成警告,因为strlen()返回的是64位的size_t类型,而int类型仍然是32位的。因此,我已经把它们替换成了

size_t len = strlen(pstr);

但我意识到这并不安全,因为size_t是无符号的,但代码可能会将其视为有符号的(实际上我遇到了一种情况,谢谢单元测试!)。

盲目地将strlen返回值强制转换为(int)感觉很不好。或许不应该这样做吗?
所以问题是: 是否有优雅的解决方案?我可能有成千上万行像这样的代码; 我不能手动检查每一个,并且测试覆盖率目前在0.01%到0.001%之间。


2
你有没有一个例子,其中这个长度被视为有符号的? - kroimon
例子大概是这样的: len--; if (len < 0) { break } - Tim
6个回答

9

一段时间前,我在我的博客上发布了一篇关于这种问题的简短说明,简单回答是:

始终使用适当的C++整数类型

详细回答: 在C++编程中,使用特定于上下文的适当整数类型是一个好主意。一点严格性总是有回报的。忽略指定为标准容器特定整数类型(如size_type)的趋势并不罕见。它适用于许多标准容器,如std::string或std::vector。这种无知可能很容易得到报复。

以下是一个简单的示例,显示了错误使用类型来捕获std::string::find函数结果。我相当确定许多人会认为这里的unsigned int没有任何问题。但实际上,这只是一个错误。我在64位架构的Linux上运行此程序时,如果按原样编译,它将按预期工作。但是,当我将1行中的字符串替换为abc时,它仍然可以工作,但不是按预期的方式:-)

#include <iostream>
#include <string>
using namespace std;
int main()
{
  string s = "a:b:c"; // "abc" [1]
  char delim = ':';
  unsigned int pos = s.find(delim);
  if(string::npos != pos)
  {
    cout << delim << " found in " << s << endl;
  }
}

修复方法非常简单。只需用std::string::size_type替换unsigned int即可。如果编写这个程序的人注意使用正确的类型,则可以避免此问题。更不用说这个程序会立即变得可移植。

我看到过这种问题很多次,尤其是由前C程序员编写的代码。他们不喜欢戴上C++类型系统所强制执行和要求的口罩。上面的例子很简单,但我相信它能很好地展示问题的根源。

我建议读一下Andrey Karpov撰写的精彩文章64-bit development,在那里您可以找到更多相关内容。


2
尽管我通常同意“使用正确的类型”,但在所有良好的实现中,std::some_container::size_type归结为size_t。据我所见,至少std::bitset::size_typestd::array::size_typestd::initializer_liststd::allocator::size_type都是size_t的typedef。因此,除非您使用疯狂的分配器或非常特殊的模板参数,否则size_t就足够了。 - rubenvb

5
作为妥协,您可以使用ssize_t(如果可用)。如果没有,可以使用long longint_fast64_tintmax_t或具有平台移植头文件的方法来指定适当的类型。 ssize_t不是标准C或C++中的内容,但如果您遇到没有与size_t相同大小的有符号类型的平台,则我表示同情。
将其转换为int几乎是安全的(假设在您的64位平台上使用32位int,这似乎是合理的),因为字符串不太可能超过2^31个字节。将其转换为更大的有符号类型甚至更加安全。能够负担2^63字节内存的客户被称为“好问题” ;-)
当然,您也可以进行检查:
size_t ulen = strlen(pstr);
if (ulen > SSIZE_MAX) abort(); // preferably trace, log, return error, etc.
ssize_t len = (ssize_t) ulen;

确实会有一些开销,但如果你有1000个实例,那么它们不都是性能关键的。对于那些(如果有的话)性能关键的实例,您可以进行调查,了解len是否实际上很重要。如果不重要,请切换到size_t。如果很重要,可以重写代码或冒险永远不会遇到那些特别巨大的对象。如果len由于strlen返回一个比INT_MAX大的值而变为负数,则原始代码在32位平台上几乎肯定会出错。


我同意将类型转换为int几乎是安全的,但我不明白ssize_t的意义所在:它也几乎是安全的。它比int稍微安全一些,但仍然存在问题 - size_t可能比ssize_t更大。 - MK.
@MK,ssize_t 必须与 size_t 大小相等。 - osgx
2
@MK:我认为ssize_t的一般意图是,在实践中,POSIX实现不会允许单个对象大于可用地址空间的一半。在malloc中很容易强制执行这一点,尽管我不认为这是有保证的。拥有一个带符号大小类型以表示允许为负的偏移量非常有用。 - Steve Jessop
3
@osgx的意思是,“更大”指的是SIZE_MAX > SSIZE_MAX,因此值可能会更大,而不是类型更大。请注意,这里的“更大”是指数值上的大小,而非数据类型的大小。 - Steve Jessop

5

将编译器警告级别设置为最高级别,可以获得每个不正确符号转换的详细报告。在gcc中,'-Wall -Wextra'应该就可以了。

您还可以使用类似cppcheck的静态代码分析工具来检查是否一切正常。


-and的-wall选项将找到所有使用带符号上下文的size_t的地方。你真的应该使用size_t。 - pm100

4
您可以使用ssize_tsize_t的有符号变体)来进行操作。

1
如果你的编译器支持 c++0x:
auto len = strlen(pstr);

1

在大多数情况下,您可以安全地处理site_t签名。当unsigned size_t(或表达式中的中间结果)大于2 ^ 31(32位)或2 ^ 63(64位)时,它将被视为负数。

更新: 抱歉,在像while ( (size_t)t >=0 )这样的结构中,size_t将不安全。因此,正确的答案是使用ssize_t


1
我指的是当我将len递减到负数时的情况。就像在循环中一样 while (len > 0) - MK.
循环 while (len>0) 应该在 len == 0 时停止。 请向我们展示您的示例,其中通过单元测试检测到了问题。 - osgx
2
抱歉,我是说如果(len < 0)。我之前的循环中使用了相反的检查条件: "如果(len < 0),跳过某些内容;" 而不是 "如果(len >= 0),执行某些操作;" - MK.

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