对变量使用较小的数据类型以节省内存是一种好习惯吗?


32

当我第一次学习C ++语言时,我了解到除了int,float等之外,这些数据类型还存在着较小或较大的版本。例如我可以称一个变量x

int x;
or 
short int x;

主要区别在于short int占用2个字节的内存,而int占用4个字节,而short int的值较小,但是我们也可以调用它来使其更小:

int x;
short int x;
unsigned short int x;

更加严格。

我的问题是,根据变量在程序中采用的值,使用单独的数据类型是否是一个好习惯。始终根据这些数据类型声明变量是一个好主意吗?


3
您知道Flyweight设计模式吗?“通过与其他类似对象共享尽可能多的数据来最大程度地减少内存使用的对象;当简单的重复表示使用不可接受的内存量时,它是大量使用对象的一种方式……”
gnat

5
使用标准打包/对齐编译器设置,无论如何这些变量都将对齐到4个字节边界,因此可能根本没有任何区别。
nikie 2012年

36
过早优化的经典案例。
围巾岭

1
@nikie-它们可能在x86处理器上的4字节边界上对齐,但这通常是不正确的。MSP430将char放在任何字节地址上,并将其他所有内容放在偶数字节地址上。我认为AVR-32和ARM Cortex-M是相同的。
uɐɪ

3
问题的第二部分意味着加法unsigned使整数占用的空间更少,这当然是错误的。它具有相同数量的离散可表示值(根据表示符号的方式,给定或取1),但仅移至正数。
underscore_d

Answers:


41

大多数情况下,空间成本可以忽略不计,您不必担心,但是您应该担心通过声明类型所提供的额外信息。例如,如果您:

unsigned int salary;

您正在向另一位开发人员提供有用的信息:薪水不能为负。

short,int和long之间的区别很少会在应用程序中引起空间问题。您很可能会意外地误认为数字将始终适合某些数据类型。始终使用int可能更安全,除非您100%确定自己的数字始终很小。即使那样,也不太可能为您节省任何数量的空间。


5
确实,这些天很少会引起问题,但是如果您要设计另一个开发人员将使用的库或类,那是另一回事了。也许他们将需要存储一百万个这样的对象,在这种情况下,差异很大-4MB相比,仅这一领域的2MB。
dodgy_coder

30
unsigned在这种情况下使用是一个坏主意:不仅薪水不能为负,而且两个薪金之间的差额也不能为负。(通常,将unsigned用作位纠结,并在溢出时定义行为,这是个坏主意。)
zvrba 2012年

15
@zvrba:两个薪金之间的差额本身并不是薪金,因此使用已签名的其他类型是合法的。
JeremyP 2012年

12
@JeremyP是的,但是如果您使用的是C(在C ++中也是如此),则无符号整数减法会导致无符号int,该值不能为负。如果将其强制转换为带符号的int,则可能会变成正确的值,但计算结果为无符号的int。另请参阅此答案以了解更多有符号/无符号的计算怪异性-这就是为什么除非您确实在纠结位,否则永远不要使用无符号变量。
Tacroy

5
@zvrba:差异是金钱数量而不是薪水。现在您可能会认为薪水也是一种货币数量(通过验证输入来约束正数和0,这是大多数人会做的事),但是两种薪金之间的差异本身并不是薪金。
JeremyP

29

OP并未透露他们正在为其编写程序的系统类型,但我认为OP是因为提到了C ++,因此正在考虑具有GB内存的典型PC。就像其中一条评论所说的那样,即使具有这种内存,如果您有数百万种类型的项(例如数组),那么变量的大小也会有所不同。

如果您进入嵌入式系统领域-但这并不是真正不在问题范围之内,因为OP并不将其限于PC-则数据类型的大小非常重要。我刚刚在一个只有8K字的程序存储器和368 字节 RAM 的8位微控制器上完成了一个快速项目。在那里,显然每个字节都很重要。一个人永远不会使用超出其所需大小的变量(从空间角度和代码大小来看,这两者都是8位处理器使用大量指令来操纵16位和32位数据)。为什么要使用资源如此有限的CPU?批量购买时,成本低至四分之一。

我目前正在用32位基于MIPS的微控制器进行另一个嵌入式项目,该微控制器具有512K字节的闪存和128K字节的RAM(数量大约为6美元)。与PC一样,“自然”数据大小为32位。现在,从代码角度来看,对大多数变量使用int而不是char或short变得更加高效。但是再一次,无论是否需要较小的数据类型,都必须考虑任何类型的数组或结构。不同的编译器对于较大的系统,它是一个结构更可能的变量被包装在嵌入式系统上。我小心翼翼地始终尝试将所有32位变量放在第一位,然后是16位,然后是8位,以避免出现任何“漏洞”。


10
+1表示不同的规则适用于嵌入式系统。提到C ++的事实并不意味着目标是PC。我最近的项目之一是用C ++在具有32k RAM和256K Flash的处理器上编写的。
uɐɪ

13

答案取决于您的系统。通常,以下是使用较小类型的优点和缺点:

好处

  • 较小的类型在大多数系统上使用较少的内存。
  • 较小的类型可以在某些系统上更快地进行计算。在许多系统上,浮点数与双精度点尤其如此。较小的int类型也可以在8位或16位CPU上提供明显更快的代码。

缺点

  • 许多CPU有对齐要求。一些访问对齐数据的速度比未对齐速度更快。有些数据必须对齐后才能访问。较大的整数类型等于一个对齐的单位,因此它们很可能不会对齐。这意味着编译器可能被迫将较小的整数放在较大的整数中。而且,如果较小的类型是较大结构的一部分,则可能会得到编译器在结构中的任何位置静默插入的各种填充字节,以解决对齐问题。
  • 危险的隐式转换。对于如何将变量提升为更大的变量,C和C ++有一些晦涩,危险的规则,隐含地没有类型转换。有两组相互缠绕的隐式转换规则,分别称为“整数提升规则”和“常规算术转换”。在此处阅读有关它们的更多信息。这些规则是导致C和C ++错误的最常见原因之一。您只需在整个程序中使用相同的整数类型,就可以避免很多问题。

我的建议是喜欢这样:

system                             int types

small/low level embedded system    stdint.h with smaller types
32-bit embedded system             stdint.h, stick to int32_t and uint32_t.
32-bit desktop system              Only use (unsigned) int and long long.
64-bit system                      Only use (unsigned) int and long long.

另外,您也可以使用stdint.h中的int_leastn_tor int_fastn_t,其中n是数字8、16、32 或64。type int_leastn_t表示“我希望此长度至少为n个字节,但我不在乎编译器是否将其分配为较大的类型以适合对齐”。

int_fastn_t 意味着“我希望它的长度为n个字节,但是如果这会使我的代码运行更快,则编译器应使用比指定的类型大的类型”。

通常,各种stdint.h类型比普通格式好得多int,因为它们是可移植的。这样做的目的int不是仅仅给它指定的宽度以使其便携。但实际上,很难移植,因为您永远都不知道特定系统上的移植量。


当场关于对齐。在我当前的项目中,在16位MSP430上随意使用uint8_t会使MCU以神秘的方式崩溃(很可能是未对齐的访问发生在某个地方,也许是GCC的错误,也许不是)-只是用“ unsigned”替换所有uint8_t消除了崩溃。如果不是致命的话,在大于8位的拱上使用8位类型至少是无效的:编译器会生成其他“ and reg,0xff”指令。使用“ int / unsigned”可移植性并使编译器摆脱额外的约束。
alexei

11

根据特定操作系统的工作方式,通常希望未优化地分配内存,这样当您调用一个字节,一个单词或其他一些小的数据类型时,该值会占用整个寄存器,这非常重要。拥有。但是,您的编译器或解释器如何工作来解释这一点还有其他事情,因此,例如,如果您要使用C#编译程序,则该值可能在物理上为其自身占用一个寄存器,但是将对该值进行边界检查以确保您不会尝试存储一个将超出预期数据类型范围的值。

从性能角度来看,如果您真的很讨厌这些事情,那么简单地使用与目标寄存器大小最匹配的数据类型可能会更快,但是随后您会错过所有可爱的语法糖,这使使用变量变得如此容易。

这对您有什么帮助?好吧,这实际上取决于您决定要针对哪种情况进行编码。对于我写过的几乎每个程序,仅信任您的编译器就可以优化事情并使用对您最有用的数据类型就足够了。如果需要高精度,请使用较大的浮点数据类型。如果仅使用正值,则可以使用无符号整数,但是在大多数情况下,仅使用int数据类型就足够了。

但是,如果您有一些非常严格的数据要求,例如编写通信协议或某种加密算法,那么使用范围检查的数据类型会非常方便,尤其是在您要避免与数据超限/欠载有关的问题时,或无效的数据值。

我想到使用特定数据类型的唯一原因就是试图在代码中传达意图。例如,如果使用shortint,则告诉其他开发人员您允许在很小的值范围内使用正数和负数。


6

正如Scarfridge所说,这是一个

过早优化的经典案例。

尝试针对内存使用进行优化可能会影响其他性能领域,并且优化黄金法则是:

程序优化的第一条规则:不要这样做

程序优化的第二条规则(仅适用于专家!):还不要这样做。”

-迈克尔·杰克逊

为了知道现在是否是优化的时候,需要进行基准测试。您需要知道代码效率低下的地方,以便您可以进行优化。

为了确定是否该优化的代码版本实际上比在任何给定时间天真的实现,需要标杆他们使用相同的数据并排侧。

另外,请记住,仅仅因为给定的实现在当前一代的CPU上效率更高,并不意味着它总是会如此。对问题的回答编码时微优化是否重要?详细介绍了一个来自个人经验的示例,其中过时的优化导致数量级下降。

在许多的处理器,对齐的内存访问是显著比对齐内存访问更加昂贵。在您的结构中打包一些短裤可能仅意味着您每次触摸任一值,程序都必须执行打包操作。

因此,现代编译器会忽略您的建议。正如nikie所说:

使用标准打包/对齐编译器设置,无论如何该变量都将对齐到4个字节边界,因此可能根本没有任何区别。

其次,猜测编译器将带来危险。

在处理TB级数据集或嵌入式微控制器时,存在进行此类优化的地方,但是对于我们大多数人来说,这并不是真正的问题。


3

主要区别在于short int占用2个字节的内存,而int占用4个字节,而short int的值较小,但是我们也可以调用它来使其更小:

这是不正确的。除了char一个字节和每个字节至少8位,以及每种类型的大小大于或等于前一个类型之外,您无法假设每种类型保留多少个字节。

堆栈变量的性能优势非常之小-无论如何它们都将被对齐/填充。

正因为如此,short并且long现在几乎没有任何用处,所以使用几乎总是更好int


当然,如果没有切割,也stdint.h可以使用int。如果您要分配庞大的整数/结构数组,那么这intX_t很有意义,因为您可以提高效率并依赖类型的大小。这一点都不为时过早,因为您可以节省兆字节的内存。


1
实际上,随着64位环境的出现,long可能与有所不同int。如果您的编译器是LP64、32 int位和long64位,则您会发现ints可能仍是4字节对齐的(例如,我的编译器确实如此)。
JeremyP 2012年

1
@JeremyP是的,我是否另外说了些什么?
Pubby 2012年

您的最后一句话声称长短几乎没有用。如果只作为int64_t
–JeremyP

@JeremyP:int和long long可以很好地生活。
gnasher729 '16

@ gnasher729:如果您需要一个变量,可以容纳超过65,000个值,但绝不超过10亿个值,该怎么办? int32_tint_fast32_tlong都是不错的选择,long long只是浪费而且int不可携带。
Ben Voigt

3

从一种OOP和/或企业/应用程序的角度来看,这可能并不适用于某些领域/领域,但是我想提出原始痴迷的概念。

对应用程序中的各种信息使用不同的数据类型是一个好主意。但是,为此使用内置类型可能不是一个好主意,除非您遇到一些严重的性能问题(已进行了测量和验证,等等)。

如果要在应用中对开尔文温度进行建模,可以使用ushortuint或类似的表示“开尔文度数为负的概念是荒谬的,并且存在域逻辑错误”。这背后的想法很合理,但是您并不会一路走好。我们意识到,我们不能有负值,因此如果可以让编译器确保没有人为开尔文温度分配负值,这将很方便。同样,您不能对温度进行按位运算。而且您不能在温度(K)上加上重量(kg)的量度。但是,如果将温度和质量都建模为uints,我们可以做到这一点。

使用内置类型来建模我们的DOMAIN实体必然会导致一些凌乱的代码,一些遗漏的检查和损坏的不变式。即使一个类型捕获了实体的某些部分(不能为负),也必然会错过其他部分(不能在任意算术表达式中使用,不能视为位数组等)。

解决方案是定义封装不变量的新类型。这样,您可以确保金钱就是金钱,距离就是距离,并且不能将它们加在一起,也不能创建负距离,但是可以创建负数的钱(或债务)。当然,这些类型将在内部使用内置类型,但这对客户端是隐藏的。关于性能/内存消耗的问题,这种事情可以让您更改内部存储的方式,而无需更改在域实体上运行的函数的接口,如果您发现该死的,a short太该死了大。


1

当然是。最好将其uint_least8_t用于字典,巨大的常量数组,缓冲区等。最好将其uint_fast8_t用于处理目的。

uint8_least_t(存储)-> uint8_fast_t(处理)-> uint8_least_t(存储)。

例如,您要从中提取8位符号,从中提取source16位代码,再提取dictionaries32位constants。比起用它们来处理10-15位运算,输出8位destination

假设您必须处理2 GB的source。位操作的数量巨大。如果在处理过程中切换到快速类型,您将获得卓越的性能奖金。每个CPU系列的快速类型可以不同。您可以包括stdint.h和使用uint_fast8_tuint_fast16_tuint_fast32_t,等。

您可以使用uint_least8_t代替uint8_t进行移植。但是实际上没有人知道现代的cpu将使用此功能。VAC机是博物馆的一件作品。所以也许这是一个过分的杀伤力。


1
尽管您可能会对列出的数据类型有所了解,但您应该解释为什么它们更好,而不是仅仅说明它们。对于像我这样不熟悉这些数据类型的人,我不得不在Google上搜索它们以了解您在说什么。
彼得M,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.