64位上的int vs size_t

MK.*_*MK. 16 c portability 32bit-64bit

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

int len = strlen(pstr);
Run Code Online (Sandbox Code Playgroud)

这些都会产生警告,因为strlen()返回的是size_t,它是64位,而int仍然是32位.所以我一直在用它们取而代之

size_t len = strlen(pstr);
Run Code Online (Sandbox Code Playgroud)

但我只是意识到这是不安全的,因为size_t是无符号的,它可以被视为代码签名(我实际上碰到了一个导致问题的情况,谢谢你,单元测试!).

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

mlo*_*kot 7

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

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

答案很长:用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;
  }
}
Run Code Online (Sandbox Code Playgroud)

修复非常简单.只需用std :: string :: size_type替换unsigned int.如果编写此程序的人负责使用正确的类型,则可以避免这个问题.更不用说该计划将立即便携.

我已经多次看到过这种问题,特别是在前C程序员编写的代码中,他们不喜欢穿C++类型系统强制执行和要求的严格的枪口.上面的例子是一个微不足道的例子,但我相信它很好地解决了问题的根源.

我推荐Andrey Karpov写的精彩文章64位开发,你可以在这里找到更多关于这个主题的文章.

  • 虽然我普遍同意"使用正确的类型",但在所有体面的实现中,`std :: some_container :: size_type`归结为`size_t`.据我所知,至少`std :: bitset :: size_type`,`std :: array :: size_type`,`std :: initializer_list`和`std :: allocator :: size_type`是typedef for` size_t`.因此,除非你使用疯狂的分配器或非常特殊的模板参数,否则`size_t`就足够了. (2认同)

pau*_*lla 5

将编译器警告设置为最大级别可以为您提供每个不正确的符号转换的良好报告.在gcc中,'-Wall -Wextra'应该这样做.

您还可以使用像cppcheck这样的静态代码分析器来查看是否一切正常.


Ste*_*sop 5

作为折衷方案,您可以使用ssize_t(如果可用).假,如果时间不使用long long,int_fast64_t,intmax_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;
Run Code Online (Sandbox Code Playgroud)

当然有一个开销,但如果你有1000个实例,那么它们不可能都是性能关键.对于那些(如果有的话),你可以做一些工作来调查len签名是否真正重要.如果没有,请切换到size_t.如果确实如此,重写或冒险从不会遇到一个荒谬的巨大对象.原始代码几乎肯定会在32位平台上做错了,如果len由于strlen返回大于的值而导致负数INT_MAX.

  • @osgx:通过"更大"他意味着`SIZE_MAX> SSIZE_MAX`,所以值可能更大.不是类型更大. (3认同)
  • @MK:我认为`ssize_t`的一般意图是,在实践中,POSIX实现不允许单个对象大于可用地址空间大小的一半.执行这个很容易就是`malloc`,虽然我不认为它是有保证的.使用带符号的大小类型来表示允许为负的偏移量是很有用的. (2认同)