size_t 这个类型的意义是什么?//笔记

使用size_t可能会提高代码的可移植性、有效性或者可读性,或许同时提高这三者

操作大数据容量的高性能可能性以及不同平台之间的可移植性

下面是我认为较为完整好理解的说法

简介//为什么有这样的东西出现

在我们开始之前,我想指出,本文中给出的定义和建议是指目前最流行的体系结构(IA-32、Intel 64、IA-64),可能并不完全适用于一些特殊的体系结构。

创建size_t和ptrdiff_t类型是为了执行正确的地址算术。很长一段时间以来,人们一直认为int的大小与计算机字(微处理器的容量)的大小一致,它可以用作存储对象或指针大小的索引。相应地,地址算法也使用int和unsigned类型来构建。int类型在大多数C和c++编程培训材料中用于循环体和作为索引。下面的例子几乎是经典的:

For (int I = 0;I < n;I ++) a[I] = 0;

随着微处理器的发展和容量的增加,进一步增加int类型的大小就变得不合理了。这有很多原因:使用的内存的经济性,最大的可移植性等。因此,出现了几个声明C/ c++基类型关系的数据模型。表N1显示了主要的数据模型,并列出了使用它们的最流行的系统。

 总结,就是说,由于不同版本的电脑系统,int,short,long这些玩意在不同位数的电脑中内存空间不同,比如说,你在Microsoft Win64(X64}IA64),就是表中第6行里面的计算机用int(这里是4字节存了一个很大的数据,这样如果在PDP-11 Unix (1973),就是表中第1行里面的计算机,用这个计算机打开的话,这样那个数据有可能就是有问题的,因为在PDP-11 Unix (1973)中,int是一个2字节的数据类型

从表中可以看出,选择一个变量的类型来存储指针或对象的大小并不是那么容易。为了找到这个问题最聪明的解决方案,创建了大小_t和ptrdiff_t类型。它们被保证用于地址计算。现在下面的代码必须成为标准:

For (ptrdiff_t I = 0;I < n;I ++) a[I] = 0;

正是这些代码提供了安全性、可移植性和良好的性能。本文的其余部分将解释其中的原因。

size_t类型

size_t类型是Cc++语言的基本无符号整数类型。//就是说他是正整数,和那个unsigned很像

整型有无符号(unsigned)和有符号(signed)两种类型,在默认情况下声明的整型变量都是有符号的类型(char有点特别),如果需声明无符号类型的话就需要在类型前加上unsigned。

无符号整型和有符号整型的区别就是无符号类型可以存放的正数范围比有符号整型中的范围大一倍,因为有符号类型将最高位储存符号,而无符号类型全都储存数字。比如16位系统中一个int能存储的数据的范围为-32768~32767,而unsigned能存储的数据范围则是0~65535。

它是sizeof操作符返回的结果的类型。

sizeof操作符的作用是返回一个对象或类型名的长度,返回值的类型为size_t,长度的单位是字节。sizeof表达式的结果是编译时常量,该操作符有一下三种语法形式:

sizeof(type name);
sizeof(expr);
sizeof expr;

选择类型的大小,以便它可以存储理论上可能的任何类型的数组的最大大小。

在32位系统上size_t为32位,而在64位系统上size_t为64位。

换句话说,size_t类型的变量可以安全地存储指针。

例外是指向类函数的指针,但这是一个特殊情况。

虽然size_t可以存储指针,但最好使用另一种无符号整数类型uintptr_t(它的名称反映了它的功能)。size_t和uintptr_t类型是同义词。Size_t类型通常用于循环计数器、数组索引和地址算术。

size_t类型的最大可能值是常量SIZE_MAX。

ptrdiff_t类型

ptrdiff_t类型是Cc++语言的带符号整数类型。

选择类型的大小,以便它可以存储理论上可能的任何类型的数组的最大大小。

在32位系统上,ptrdiff_t是32位,在64位系统上是64位。

与size_t一样,ptrdiff_t可以安全地存储指针,但指向类函数的指针除外。

另外,ptrdiff_t是一个表达式的结果类型,其中一个指针减去另一个指针(ptr1-ptr2)。

Ptrdiff_t类型通常用于循环计数器、数组索引、大小存储和地址算术。

Ptrdiff_t类型有其同义词intptr_t,其名称更清楚地表明它可以存储指针。

size_t和ptrdiff_t的可移植性

size_t和ptrdiff_t类型使您能够编写可移植性良好的代码。使用size_t和ptrdiff_t类型创建的代码很容易移植。size_t和ptrdiff_t的大小总是与指针的大小一致。正因为如此,这些类型应该被用作大型数组的索引,用于存储指针和指针算术。

linux应用程序开发人员通常使用长类型来实现这些目的。在Linux中接受的32位和64位数据模型框架中,这确实是可行的。Long类型的大小与指针的大小一致。但是这段代码与Windows数据模型不兼容,因此您不能认为它易于移植。更正确的解决方案是使用类型size_t和ptrdiff_t。

作为size_t和ptrdiff_t的替代,windows开发人员可以使用类型DWORD_PTR, size_t, SSIZE_T等。但是,限制为size_t和ptrdiff_t类型仍然是可取的。

ptrdiff_t和size_t类型在地址算术中的安全性

自64位系统开始适应以来,地址算术问题经常发生。将32位应用程序移植到64位系统中的大多数问题都与int和long等类型的使用有关,这些类型不适合使用指针和类型数组。将应用程序移植到64位系统的问题并不局限于此,但大多数错误都与地址算术和索引操作有关。

这里有一个简单的例子:

Size_t n =…For (unsigned I = 0;I < n;I ++) a[I] = 0;

如果我们处理的数组包含超过UINT_MAX项,这段代码是不正确的。检测错误并预测这段代码的行为并不容易。调试版本将挂起,但几乎没有人会在调试版本中处理千兆字节的数据。而发布版本(取决于优化设置和代码的特性)可以挂起,也可以突然正确地填充所有数组单元格,从而产生正确操作的错觉。因此,在程序中出现浮动错误,随着代码的细微更改而发生和消失。要了解更多关于这种虚幻错误及其危险后果的信息,请参阅文章“一匹64位的马,可以计数”[1]。

另一个“休眠”错误的例子,发生在输入数据的特定组合(a和B变量的值):

int A = -2;
unsigned B = 1;
Int数组[5]= {1,2,3,4,5};
Int *ptr = array + 3;
ptr = ptr + (A + B);
//错误printf("%i\n", *ptr);

这段代码将在32位版本中正确执行,并打印数字“3”。在64位模式下编译后,执行代码时会出现失败。让我们检查一下代码执行的顺序和错误的原因:

  • int类型的变量被强制转换为unsigned类型;
  • A和B相加。结果,我们得到unsigned类型的0xFFFFFFFF值;
  • 计算“ptr + 0xFFFFFFFFu”表达式。结果取决于当前平台上指针的大小。在32位程序中,表达式将等于"ptr - 1",我们将成功地打印数字3。在64位程序中,0xFFFFFFFFu值将被添加到指针中,结果,指针将远远超出数组的限制。

这种错误可以通过使用size_t或ptrdiff_t类型轻松避免。在第一种情况下,如果“i”变量的类型是size_t,就不会有无限循环。在第二种情况下,如果我们对“A”和“B”变量使用size_t或ptrdiff_t类型,我们将正确地打印数字“3”。

让我们制定一个指导方针:无论在哪里处理指针或数组,都应该使用size_t和ptrdiff_t类型。

要了解有关使用size_t和ptrdiff_t类型可以避免的错误的更多信息,请参阅以下文章:

  • 在64位平台[2]上移植c++代码的20个问题;
  • 64位代码[3]的安全性;
  • C和c++代码迁移到64位Windows[4]期间的陷阱检测。

使用ptrdiff_t和size_t的代码性能

除了代码安全之外,在地址算术中使用ptrdiff_t和size_t类型还可以额外提高性能。例如,使用int类型作为索引,前者的容量与指针的容量不同,将导致二进制代码包含额外的数据转换命令。我们说的是64位代码,其中指针的大小是64位,而int类型的大小仍然是32位。

举例说明size_t类型相对于unsigned类型的优势是很困难的。为了客观,我们应该使用编译器的优化功能。而优化代码的两种变体经常因为太过不同而无法显示这种差异。我们在第六次尝试时才创建了一个简单的例子。这个例子仍然不理想,因为它演示的不是我们上面提到的那些不必要的数据类型转换,而是当使用size_t类型时,编译器可以构建更有效的代码。让我们考虑一个程序代码,以反序排列数组的项:

无符号arraySize;
For (unsigned I = 0;i < arraySize / 2;i++)
{浮点值=数组[I];
array[i] = array[arraySize - i - 1];
数组[arraySize - i - 1] = value;}

在本例中,"arraySize"和"i"变量具有unsigned类型。这种类型可以很容易地用size_t类型替换,现在比较图1所示的一小段汇编程序代码。

图N1。使用unsigned类型和size_t类型时64位汇编程序代码的比较

当使用64位寄存器时,编译器设法构建更简洁的代码。我不确定使用unsigned类型创建的代码会比使用size_t创建的代码运行得慢。比较现代处理器上的代码执行速度是一项非常困难的任务。但是从示例中可以看到,当编译器使用64位类型操作数组时,它可以构建更短、更快的代码。

根据我自己的经验,我可以说,用ptrdiff_t和size_t合理地替换int和unsigned类型,可以在64位系统上为您提供高达10%的额外性能增益。在“在Visual c++中开发资源密集型应用程序”[5]文章的第四节中,您可以看到使用ptrdiff_t和size_t类型时提高速度的示例。

代码重构,目的是转移到ptrdiff_t和size_t

正如读者所看到的,使用ptrdiff_t和size_t类型为64位程序提供了一些优势。但是,它并不是用size_t类型替换所有无符号类型的全面解决方案。首先,它不能保证程序在64位系统上的正确操作。其次,由于这种替换,很可能会出现新的错误,违反数据格式兼容性等等。您不应该忘记,在此替换之后,程序所需的内存大小也将大大增加。增加必要的内存大小将减慢应用程序的工作,因为缓存将存储更少的对象。

因此,在旧代码中引入ptrdiff_t和size_t类型是一项需要大量时间的逐步重构任务。实际上,您应该通读整个代码并进行必要的修改。实际上,这种方法既昂贵又低效。有两种可能的变体:

  • 使用PVS-Studio等专业工具。PVS-Studio是一个静态代码分析器,用于检测在64位系统上替换数据类型以使程序变得正确并有效工作的部分。
  • 如果您不打算为64位系统调整32位程序,那么数据类型重构就没有任何意义。32位程序不会从使用ptrdiff_t和size_t类型中获得任何好处。

说法1,

在标准C库中的许多函数使用的参数或者返回值都是表示的用字节表示的对象大小,比如说malloc(n) 函数的参数n指明了需要申请的空间大小,还有memcpy(s1, s2, n)的最后一个参数,表明需要复制的内存大小,strlen(s)函数的返回值表明了以’\0’结尾的字符串的长度(不包括’\0’),其返回值并不是该字符串的实际长度,因为要去掉’\0’。
  或许你会认为这些参数或者返回值应该被申明为int类型(或者long或者unsigned),但是事实上并不是。C标准中将他们定义为size_t。标准中记载malloc的申明应该出现在,定义为:

void *malloc(size_t n);

  memcpy和strlen的申明应该出现在中:

void *memcpy(void *s1, void const *s2, size_t n);
size_t strlen(char const *s);

  size_t还经常出现在C++标准库中,此外,C++库中经常会使用一个相似的类型size_type,用的可能比size_t还要多。

size_t类型是一个类型定义,通常将一些无符号的整形定义为size_t,比如说unsigned int或者unsigned long,甚至unsigned long long。每一个标准C实现应该选择足够大的无符号整形来代表该平台上最大可能出现的对象大小。

size_t和unsigned int有所不同,size_t的取值range是目标平台下最大可能的数组尺寸

一些平台下size_t的范围小于int的正数范围,又或者大于unsigned int.

最典型的,在x64下,int还是4,但size_t是8.

这意味着你在x64下最大可能开辟的数组尺寸是2^64.

如果你使用int或者unsigned int,那么在x64下如果你的代码中全部使用uint作为数组的尺寸标记,

那么你就会失去控制2^32尺寸以上的数组的机会.


说法2

学过计算机组成原理应该不会对此有疑问。int小于等于数据线宽度,size_t大于等于地址线宽度。

size_t存在的最大原因可能是因为:地址线宽度历史中经常都是大于数据线宽度的。

在数据只有8位的年代,地址率先进入10位,12位,在数据16位的年代,地址也已经进入了20位,24位。

目前的int普遍是32位,而size_t在主流平台中都是64位。

size_t为什么存在?因为无论int还是unsigned都很可能小于size_t需要的大小,所以必须有个size_t。

对_t有疑惑。

这个问题很简单,仅仅是因为作者选择这样的命名作为编码规范而已。类型名与变量名共享相同的命名空间,所以通常需要在命名方面刻意区分出来。

在遥远的 C 时代,发明者很可能是想建议所有的类型名后面加_t,只不过这并没有成为更普遍的编码规范罢了。而现今Java的规范倒比较容易让人接受:大写开头的是类型名,小写开头的是变量名跟函数名,虽然具体细则有不同,但原意都是一样的:变量与类型共享同一个命名空间,因而需要在命名规则上刻意区分开来。

_t的意思显然就是type,这没啥好解释的。关于为什么要加_t。

一个类型后面加了_t说明了这是一个POSIX或GNU保留类型,防止namespace pollution的。

不然标准库里新加了什么类型说不定就和用户已经定义的类型重名了。

所以POSIX规定自己扩展的类型都加_t,这样只要用户定义类型的时候不加_t就不会冲突。

总结

因为unsigned int不是唯一的无符号整数类型。size_t可以是任意的unsigned charunsigned shortunsigned intunsigned longunsigned long long,取决于实施。

参考文献

引用:https://www.zhihu.com/question/24773728/answer/210728099

https://www.zhihu.com/question/24773728/answer/210659978

https://www.zhihu.com/question/24773728/answer/28920149

About size_t and ptrdiff_t (pvs-studio.com)

  • 安德烈卡尔波夫。64位会数数的马http://www.viva64.com/en/a/0043/
  • Andrey Karpov, Evgeniy Ryzhkov,在64位平台上移植c++代码的20期。http://www.viva64.com/en/a/0004/
  • 安德烈卡尔波夫。64位代码的安全性。http://www.viva64.com/en/a/0046/
  • 安德烈·卡尔波夫,叶夫根尼·雷日科夫。C和c++代码迁移到64位Windows时的陷阱检测。http://www.viva64.com/en/a/0012/
  • 安德烈·卡尔波夫,叶夫根尼·雷日科夫。在Visual c++中开发资源密集型应用程序。http://www.viva64.com/en/a/0018/

【信息学奥赛教程】size_t和ptrdiff_t_哔哩哔哩_bilibili

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值