【问题标题】:int vs size_t on 64bit64 位上的 int 与 size_t
【发布时间】:2011-01-31 22:40:13
【问题描述】:

将代码从 32 位移植到 64 位。很多地方都有

int len = strlen(pstr);

这些现在都会产生警告,因为 strlen() 返回 size_t 是 64 位,而 int 仍然是 32 位。所以我一直用

替换它们
size_t len = strlen(pstr);

但我刚刚意识到这是不安全的,因为 size_t 是无符号的,它可以被代码视为已签名(我实际上遇到了一个导致问题的案例,谢谢,单元测试!)。

盲目地将 strlen 返回到 (int) 感觉很脏。或者不应该?
所以问题是:有没有一个优雅的解决方案?我的代码库中可能有一千行这样的代码;我无法手动检查它们中的每一个,并且测试覆盖率目前介于 0.01 和 0.001% 之间。

【问题讨论】:

  • 你有这个长度被视为有符号的例子吗?
  • 这个例子大概是这样的:len--; if (len < 0) { break }

标签: c portability 32bit-64bit


【解决方案1】:

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

Always use proper C++ integer types

长答案: 在 C++ 中编程时,最好使用与特定上下文相关的适当整数类型。一点点的严格总是有回报的。忽略定义为特定于标准容器的整数类型的趋势并不罕见,即 size_type。它适用于标准容器的数量,例如 std::string 或 std::vector。这种无知很容易得到报复。

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

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

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

我已经多次看到此类问题,尤其是在以前的 C 程序员编写的代码中,他们不喜欢戴上 C++ 类型系统强制执行和要求的严格枪口。上面的例子是一个微不足道的例子,但我相信它很好地说明了问题的根源。

我推荐由 Andrey Karpov 撰写的精彩文章 64-bit development,您可以在其中找到有关该主题的更多信息。

【讨论】:

  • 虽然我普遍同意“使用正确的类型”,但std::some_container::size_type 在所有体面的实现中归结为size_t。据我所知,至少std::bitset::size_typestd::array::size_typestd::initializer_liststd::allocator::size_typesize_t 的类型定义。因此,除非您使用疯狂的分配器或非常特殊的模板参数,否则size_t 就足够了。
【解决方案2】:

作为妥协,您可以使用ssize_t(如果可用)。如果没有,请使用long longint_fast64_tintmax_t 或使用平台移植标头来为平台指定合适的类型。 ssize_t 在 POSIX 中不是标准 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。如果是这样,重写或者只是冒险永远不会遇到一个荒谬的巨大对象。如果lenstrlen 返回的值大于INT_MAX 而为负数,那么原始代码几乎肯定会在32 位平台上做错事。

【讨论】:

  • 我同意强制转换为 int 几乎是安全的,但我不明白 ssize_t 的意义何在:它也nerly 安全。它比 int 稍微安全一些,但仍然 -- size_t 可以比 ssize_t 大。
  • @MK, ssize_t 的大小必须与size_t 相同
  • @MK:我认为ssize_t 的总体意图是在实践中,POSIX 实现不允许单个对象大于可用地址空间大小的一半。很容易强制执行 malloc,尽管我认为这不能保证。有一个带符号的大小类型来表示允许为负的偏移量是很有用的。
  • @osgx:“更大”是指SIZE_MAX &gt; SSIZE_MAX,因此值可能更大。并不是说类型更大。
【解决方案3】:

将编译器警告设置为最高级别应该可以为您提供每次错误符号转换的良好报告。在 gcc 中,'-Wall -Wextra' 应该可以。

您还可以使用 cppcheck 之类的静态代码分析器来查看是否一切正常。

【讨论】:

  • 和 -wall 将找到所有在签名上下文中使用 size_t 的地方。你真的应该使用 size_t
【解决方案4】:

您可以使用ssize_tsize_t 的签名变体)。

【讨论】:

    【解决方案5】:

    在大多数情况下,您可以安全地对待已签名的 site_t。只有当它(或表达式中的中间结果)大于 2^31(对于 32 位)或 2^63(对于 64 位)时,无符号 size_t 才会被视为负数。

    更新: 抱歉,size_t 在 while ( (size_t)t &gt;=0 ) 这样的结构中是不安全的。所以正确的答案是使用ssize_t

    【讨论】:

    • 我的意思是我将 len 递减到它变为负数的程度。就像在循环中一样 while (len > 0)
    • 循环 while (len&gt;0) 应该在 len == 0 处停止。请向我们展示您的示例,单元测试检测到的问题。
    • 废话,对不起,我的意思是如果 (len = 0)做某事;”
    【解决方案6】:

    如果你的编译器支持 c++0x:

    auto len = strlen(pstr);
    

    【讨论】:

      猜你喜欢
      • 2011-08-14
      • 2010-09-13
      • 2021-10-17
      • 2011-09-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-02
      • 2011-03-16
      相关资源
      最近更新 更多