为什么所有数据类型都是2的幂次方?

7

为什么所有数据类型的大小都是2的幂次方?

我们来看两个例子:

short int 16
char 8

为什么它们不像以下这样呢?
short int 12

2
我认为存在这样一个系统,其中CHAR_BIT==9,而int为36位。 - dreamlax
1
还有许多DSP的字长为24位。 - Paul R
1
当你想到它时,将事物变成二的幂次方就是有道理的,因为一切都是二进制的,也就是基于二。所以(大多数)事物通常都是以二为底的幂次方。 - Andrew Marshall
当您可以使用简单的位/位移/位掩码操作来操作数据类型大小、对齐、页面文件大小等时,所有这些都更容易实现。不需要使用过于昂贵的乘除法,也没有人想要生产慢速硬件。 - fazo
并非所有的大小都是2的幂。位数字节数都可以不是2的幂。 - phuclv
10个回答

11

那只是一些实现细节,并不总是这样。一些奇特的架构拥有非二的数据类型。例如,36位字长曾经很常见。

如今,二的幂次方之所以几乎普遍,是因为它通常简化了内部硬件实现。举个假设的例子(我不从事硬件设计,所以必须承认这主要是猜测),一个操作码表示其参数大小的部分可以被存储为该参数字节数的二的幂次方索引,因此两个比特就足以表达参数是8、16、32或64位中的哪一个,而将其转换为适当的锁存信号所需的电路将非常简单。


这两个操作码位同样可以轻松地指定12、24、36或48位。正是选择了8位最小单位,才使得所有东西的大小都像今天这样。 - John Ripley
一个36位的字仍然是2的幂(但不是256的幂(不是整数个字节))。 (在这个上下文中,“是”表示“可以表示该范围的整数值”) - Peter Mortensen

3
内置类型的大小之所以是这样,仅仅是因为CPU本地支持这个大小,也就是最快和最简单的方式。没有其他原因。
至于结构体,你可以在其中拥有几乎任意数量的位数的变量,但通常情况下,除非有真正紧急的原因,否则你将希望保持整数类型。
你还通常希望将相同大小的类型分组,并从最大类型(通常是指针)开始构建结构体。这将避免不必要的填充,并确保您不会遇到一些CPU访问惩罚与不对齐字段(某些CPU甚至可能触发未对齐访问异常,但在这种情况下,编译器会添加填充以避免它)。

4
你的意思是 某些 CPU 原生支持,对吗? - Cody Gray
如果“某些”意味着99.9%的所有现有系统和100%的所有系统,除非您是收藏家,否则您可能永远不会遇到,那么是的,有些。 :-) 具有24位或36位整数或7位字符的架构并不常见,您必须承认。 - Damon
1
不,这不是“some”的意思。这也不准确。把头埋在沙子里假装其他架构不存在是一个巨大的错误。假设实现细节就不是成为更好的程序员的方法,而这应该始终是目标。在“我的经验中不常见”和“99.9%的所有现有系统”之间存在很大的区别。 - Cody Gray
@Cody:在这一点上,我不得不与您不同意。地球上绝大多数程序员从未并且永远不会遇到非POT架构,并且大量假定POT的代码已经编写并将继续编写以应对可预见的未来。由于POT数据大小在桌面、服务器和主流移动设备中普遍存在,因此这种假设并不是不合理的。当然,了解其他体系结构很好,但这并不意味着假设POT是错误的。有时它可以帮助提高性能。 - Marcelo Cantos
类比一下:这就好像某人问为什么电视(和高清信号、DVD等等)的分辨率是720(p/i)和1080(p/i),为什么是这些奇怪的数字,为什么不是其他的,比如1000。如果你告诉他们“因为720是480的1.5倍,而480曾经是电视上的标准分辨率,而1080是720的1.5倍”,那么人们会说“啊哈,当然,这很有道理”。但如果你说“嗯,你知道PAL也有625行,而且还有黑白电视...”,那肯定是正确的,但这能让人理解为什么有1080p吗? - Damon
显示剩余3条评论

3
char、short、int、long等数据类型的大小因平台而异。32位架构通常char=8,short=16,int=32,long=32。64位架构通常char=8,short=16,int=32,long=64。
许多DSP没有2的幂次方类型。例如,Motorola DSP56k(现在有点过时)具有24位字。针对此架构的编译器(来自Tasking)使用char=8,short=16,int=24,long=48。为了使事情更加混乱,他们将char=24,short=24,int=24,long=48的对齐方式。这是因为它没有字节寻址:最小可访问单元为24位。当您确实需要在紧凑数据数组中访问8位字节时,这具有令人兴奋(恼人的)特性,涉及大量的除以3和模3。
您只会在专用核心中找到非2的幂次方,其中大小被定制以适应特殊的使用模式,从而提高性能和/或功率优势。在56k的情况下,这是因为有一个乘加单元,它可以在3个总线同时进行单周期加载两个24位数量并将它们添加到48位结果中。整个平台都是围绕它设计的。
大多数通用架构使用2的幂次方的根本原因是它们标准化了八位字节(除标志外的最小大小类型)。没有理由它不能是9位,正如其他地方指出的那样,24位和36位很常见。这将渗透到设计的其余部分:如果x86是9位字节,则我们将拥有36个八位缓存行,4608个八位页面,并且569KB对于每个人都足够 :)不过,我们可能不会有“半字节”,因为您无法将9位字节分成两半。
然而,现在几乎不可能做到这一点。从一开始就像这样设计系统很好,但与由8位字节系统生成的数据进行互操作将是一场噩梦。在24位DSP中解析8位数据已经很困难了。

选择24位当然是因为它被设计成音频处理器。实际上,56k上的累加器宽度为56位(因此得名)。因此,它甚至不是8位类型的简单倍数!原理是:您可以将两个24位数字相乘以获得48位的答案,然后在需要溢出检查之前将它们加起来256次。聪明的设计,一切都围绕它设计。 - John Ripley
编辑我的答案以包括没有必须是2的幂次方的基本原因。虽然我很高兴它确实是! - John Ripley

2

他们是2的幂次方,因为它们是8的倍数,这(稍微简化一下)来自于内存中通常的原子分配单元是字节,而(修正:通常情况下是由8个位组成的)。

更大的数据大小通过一次取多个字节来创建。 所以你可以有8、16、24、32...等数据大小。

然后,出于提高内存访问速度的考虑,只使用2的幂次方作为最小尺寸(8)的乘数,因此你得到了这些数据大小:

 8 => 8 * 2^0 bits => char
16 => 8 * 2^1 bits => short int
32 => 8 * 2^2 bits => int
64 => 8 * 2^3 bits => long long int

我说我在简化,因为显然不是 DSP 的情况。但我不明白为什么会有 -1。 - garph0
我没有投反对票,但是考虑到第一句话的前半部分是完全错误的(24不是2的幂),我认为投反对票并不过分。 - Marcelo Cantos
1
谁说24是2的幂次方了?我说24是8的倍数。 - garph0
-1 是我给的,因为你说“...一个字节,由8个比特组成。”这是不正确和误导性的。如果你改正了这个问题,我会撤销这个-1。 - Paul R
@Paul R:正如我之前回复你的评论一样,我说过我为了简单起见简化了问题,因为在我看来Vivek谈论的是最常见的x86情况。无论如何,我想这可能不够清楚,所以我会添加一条注释。 - garph0

1

8位是字节最常见的大小(但不是唯一的大小,9位字节和其他字节大小的例子也很容易找到)。更大的数据类型几乎总是字节大小的倍数,因此它们通常在具有8位字节的系统上为16、32、64、128位,但不总是2的幂,例如DSP常用24位,并且存在80位和96位浮点类型。


0
标准整数类型的大小定义为8位的倍数,因为一个字节是8位(有极少数例外),CPU的数据总线通常是8位宽的。如果您真的需要12位整数,则可以在结构体(或联合体)中使用位域,如下所示:
struct mystruct
{
    short int twelveBitInt : 12;
    short int threeBitInt  :  3;
    short int bitFlag      :  1;
};

这在嵌入式/低级环境中非常方便 - 但请记住,结构的总大小仍将被打包到完整大小。


0

它们不一定是这样的。在某些机器和编译器上,sizeof(long double) == 12(96位)。


0

并非所有数据类型都需要使用2的幂作为位数来表示。例如,long double 使用80位(尽管其实现取决于分配多少位)。

使用2的幂的一个优点是,较大的数据类型可以表示为较小的数据类型。例如,4个字符(每个字符8位)可以组成一个int(32位)。事实上,一些编译器曾经使用两个32位数字来模拟64位数字。


0
大多数情况下,您的计算机会尝试将所有数据格式保持为机器数据大小的整数倍(2、3、4...)或整数部分(1/2、1/3、1/4...)。它这样做是为了每次加载N个数据字时,为您加载整数位的信息比特数。这样,它就不必在以后重新组合部分。
例如,您可以在x86中看到这一点:
- char 是32位的1/4 - short 是32位的1/2 - int / long 是32位整数 - long long 是2个32位 - float 是单个32位 - double 是两个32位 - long double 可能是三或四个32位,具体取决于编译器设置。这是因为对于32位机器,加载96位需要三个本地机器字(因此没有开销)。在64位机器上,它是1.5个本地机器字,因此128位更有效(无需重新组合)。在x86上,long double 的实际数据内容为80位,因此这两个都已填充。
最后一点,计算机并不总是以其本地数据大小加载。它首先获取缓存行,然后从其中读取本机字。缓存行较大,通常为64或128个字节。将有意义的数据位放入其中非常有用,而不会被卡在边缘上,因为您必须加载两个完整的缓存行才能读取它。这就是为什么大多数计算机结构的大小都是2的幂次方;它将适合任何2的幂次方大小的存储器中,无论是一半、完全、双倍还是更多 - 您保证永远不会停留在边界上。

0

有一些情况下,整数类型必须是2的幂次方。如果<stdint.h>中存在精确宽度类型,例如int16_tuint32_t,它们的宽度必须恰好为该大小,没有填充。声明遵循IEEE标准的浮点数运算强制floatdouble成为2的幂次方(尽管long double通常不是)。此外,现在标准库或内置于C++中还有char16_tchar32_t等类型,定义为精确宽度类型。对于支持UTF-8的要求实际上意味着charunsigned char必须恰好为8位宽。

实际上,许多旧代码已经无法在不支持8、16、32和64位宽度类型的任何计算机上运行。例如,任何读取或写入ASCII或尝试连接到网络的程序都将崩溃。

一些具有历史意义的大型机和小型计算机的本地字长是3的倍数,而不是2的幂,特别是DEC PDP-6、PDP-8和PDP-10。

这是八进制在计算中曾经流行的主要原因:由于每个八进制数字代表三个比特,因此9、12、18或36位模式可以更整洁地用八进制数字表示,而不是十进制或十六进制。例如,当使用基于64位的六位字符打包而不是八位时,每个打包字符占用两个八进制数字。

这些体系结构最明显的遗产是,在C语言中,默认情况下,诸如'\123'之类的字符转义被解释为八进制而不是十进制,并且Unix文件权限/掩码表示为三个或四个八进制数字。


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