为啥使用short不好
Posted
技术标签:
【中文标题】为啥使用short不好【英文标题】:Why is it bad to use short为什么使用short不好 【发布时间】:2018-11-06 14:13:55 【问题描述】:即使在开发人员保证变量永远不会超过一个字节,有时是两个字节的脚本中,这种情况也很常见;
许多人决定为每个可能的变量使用int
类型来表示0-1 范围内的数字。
为什么改用char
或short
会如此痛苦?
我想我听到有人说int
是“更标准”的类型……类型。
这是什么意思。我的问题是数据类型int
是否比short
(或其他较小的数据类型)有任何明确的优势,因为这些优势,人们过去几乎总是求助于int
?
【问题讨论】:
当对象的大数组可能时使用short
是有意义的,否则,与int
相比几乎没有优势。
int
通常代表 CPU 的自然整数数据类型,因此涉及int
的操作往往会被 CPU 和编译器优化。
通常选择 int 的本机大小以匹配处理器寄存器的最佳匹配。 short
不占用完整的寄存器,因此效率较低。使用适合您正在做的事情的大小。如果您有使用整数以外的特定要求,请执行此操作。否则,请相信编译器会完成它的工作。
这还不错,也没有什么伤害,很多,除非你有大量相邻的它们,否则它是没有意义的,例如。在一个数组中,或者是一些具有数百个相邻short
成员的疯狂对象。或者,如果您出于某种原因专门进行 16 位算术运算,例如基数转换。在某些情况下它是唯一正确的选择。
@PSkocik 我在上面发布的链接提供了一些示例来说明。
【参考方案1】:
作为一般规则,C 中的大多数算术都是使用类型 int
执行的(即普通的 int
,而不是 short
或 long
)。这是因为 (a) C 的定义是这样说的,这与 (b) 这是许多处理器(至少是 C 的设计者所考虑的)更喜欢工作的事实有关。
因此,如果您尝试通过使用 short
ints 来“节省空间”,并且您编写类似
short a = 1, b = 2;
short c = a + b;
编译器必须发出代码,实际上将a
从short
转换为int
,将b
从short
转换为int
,进行加法,并将总和转换回short
。您可能为a
、b
和c
节省了一点存储空间,但您的代码可能会更大(并且更慢)。
如果你改为写
int a = 1, b = 2;
int c = a + b;
您在a
、b
和c
中花费了更多存储空间,但代码可能更小更快。
这有点过于简单化了,但在您的观察背后,short
类型的使用很少见,通常建议使用普通的int
。基本上,因为它是机器的“自然”大小,所以它被认为是最直接的算术类型,不需要额外的与不太自然的类型之间的转换。这有点像“在罗马时,像罗马人那样做”的论点,但它通常确实使用普通的int
有利。
另一方面,如果您有 很多 个不太大的整数要存储(它们的大型数组,或包含不太大整数的大型结构数组),为数据节省的存储空间可能很大,但与代码大小(相对较小)的增加以及潜在的速度增加相比,这是值得的。
另请参阅this previous SO question 和 this C FAQ list entry。
附录:与任何优化问题一样,如果您真的关心数据空间使用、代码空间使用和代码速度,您将需要使用您的确切机器和处理器进行仔细测量。毕竟,您的处理器可能最终不需要任何“额外的转换指令”来转换为较小的类型/从较小的类型转换,因此使用它们可能并不是那么不利。但同时您也可以确认,对于孤立变量,使用它们也可能不会产生任何可衡量的优势。
附录 2。这是一个数据点。我试验了代码
extern short a, b, c;
void f()
c = a + b;
我用两个编译器编译,gcc 和 clang(为 Mac 上的 Intel 处理器编译)。然后我将short
更改为int
并再次编译。 int
-using 代码在 gcc 下小 7 个字节,在 clang 下小 10 个字节。检查汇编语言输出表明不同之处在于截断结果以便将其存储在c
;获取 short
而不是 int
似乎不会改变指令数。
但是,我随后尝试调用这两个不同的版本,发现它在运行时间上几乎没有区别,即使在 10000000000 次调用之后也是如此。因此,“使用short
可能会使代码变大”部分答案得到了确认,但可能不是“并且还让它变慢了”。
【讨论】:
有道理,我知道空间:性能之间的相关性。缓存是提高性能和解析以减少空间消耗的最通用方法。 @Edenia Typeint
的定义优势是它被认为是最“自然”和最有效的算术类型。任何其他不太“自然”的类型可能需要额外的转换或效率较低的算术。我想你知道这一点,所以如果你问是否有任何其他,不太明显的优势,我想答案是“不,只是'自然'。”
是的,我很惊讶为什么没有多少人想知道为什么每个人都在书籍、教程、开源项目中使用整数,而还有其他数据类型意味着较低的值。对于像 Object Pascal 这样的高级抽象语言,这一点更加明显。
嗯?您毫无条件地说“编译器必须发出代码”和“您的代码更大更慢”。三十年来我没有认真处理过这个问题,但当时不是真的,现在也不是。
@EJP 好吧,我已经这样做了至少 30 年了,虽然我没有听从自己的建议,因为我从未进行过任何仔细的测量来确认效果. (我基本上只是在模仿党的路线,这就是我说“在罗马时,像罗马人那样做”的原因之一。)但我刚才做了一些测量,实际上,使用short
确实 使代码更大。我会尽快发布结果。【参考方案2】:
我怀疑基于短的代码应该以任何重要的方式变得更慢和更大的说法(假设这里是局部变量,没有关于short
s 在适当情况下肯定会得到回报的大型数组的争议),所以我尝试了在我的Intel(R) Core(TM) i5 CPU M 430 @ 2.27GHz
上做基准测试
我用过(long.c):
long long_f(long A, long B)
//made up func w/ a couple of integer ops
//to offset func-call overhead
long r=0;
for(long i=0;i<10;i++)
A=3*A*A;
B=4*B*B*B;
r=A+B;
return r;
在基于long
、int
和short
的版本(%s/long/TYPE/g
) 中,使用-O3
和-Os
中的gcc
和clang
构建程序并测量大小和运行时间每个函数的 1 亿次调用。
f.h:
#pragma once
int int_f(int A, int B);
short short_f(short A, short B);
long long_f(long A, long B);
main.c:
#include "f.h"
#include <stdlib.h>
#include <stdio.h>
#define CNT 100000000
int main(int C, char **V)
int choose = atoi(V[1]?:"0");
switch(choose)
case 0:
puts("short");
for(int i=0; i<CNT;i++)
short_f(1,2);
break;
case 1:
puts("int");
for(int i=0; i<CNT;i++)
int_f(1,2);
break;
default:
puts("long");
for(int i=0; i<CNT;i++)
long_f(1,2);
构建:
#!/bin/sh -eu
time() command time -o /dev/stdout "$@";
for cc in gcc clang; do
$cc -Os short.c -c
$cc -Os int.c -c
$cc -Os long.c -c
size short.o int.o long.o
$cc main.c short.o int.o long.o
echo $cc -Os
time ./a.out 2
time ./a.out 1
time ./a.out 0
$cc -O3 short.c -c
$cc -O3 int.c -c
$cc -O3 long.c -c
size short.o int.o long.o
$cc main.c short.o int.o long.o
echo $cc -O3
time ./a.out 2
time ./a.out 1
time ./a.out 0
done
我做了两次,结果似乎很稳定。
text data bss dec hex filename
79 0 0 79 4f short.o
80 0 0 80 50 int.o
87 0 0 87 57 long.o
gcc -Os
long
3.85user 0.00system 0:03.85elapsed 99%CPU (0avgtext+0avgdata 1272maxresident)k
0inputs+0outputs (0major+73minor)pagefaults 0swaps
int
4.78user 0.00system 0:04.78elapsed 99%CPU (0avgtext+0avgdata 1220maxresident)k
0inputs+0outputs (0major+74minor)pagefaults 0swaps
short
3.36user 0.00system 0:03.36elapsed 99%CPU (0avgtext+0avgdata 1328maxresident)k
0inputs+0outputs (0major+74minor)pagefaults 0swaps
text data bss dec hex filename
137 0 0 137 89 short.o
109 0 0 109 6d int.o
292 0 0 292 124 long.o
gcc -O3
long
3.90user 0.00system 0:03.90elapsed 99%CPU (0avgtext+0avgdata 1220maxresident)k
0inputs+0outputs (0major+74minor)pagefaults 0swaps
int
1.22user 0.00system 0:01.22elapsed 99%CPU (0avgtext+0avgdata 1260maxresident)k
0inputs+0outputs (0major+73minor)pagefaults 0swaps
short
1.62user 0.00system 0:01.62elapsed 99%CPU (0avgtext+0avgdata 1228maxresident)k
0inputs+0outputs (0major+73minor)pagefaults 0swaps
text data bss dec hex filename
83 0 0 83 53 short.o
79 0 0 79 4f int.o
88 0 0 88 58 long.o
clang -Os
long
3.33user 0.00system 0:03.33elapsed 99%CPU (0avgtext+0avgdata 1316maxresident)k
0inputs+0outputs (0major+71minor)pagefaults 0swaps
int
3.02user 0.00system 0:03.03elapsed 99%CPU (0avgtext+0avgdata 1316maxresident)k
0inputs+0outputs (0major+71minor)pagefaults 0swaps
short
5.27user 0.00system 0:05.28elapsed 99%CPU (0avgtext+0avgdata 1236maxresident)k
0inputs+0outputs (0major+69minor)pagefaults 0swaps
text data bss dec hex filename
110 0 0 110 6e short.o
219 0 0 219 db int.o
279 0 0 279 117 long.o
clang -O3
long
3.57user 0.00system 0:03.57elapsed 99%CPU (0avgtext+0avgdata 1228maxresident)k
0inputs+0outputs (0major+69minor)pagefaults 0swaps
int
2.86user 0.00system 0:02.87elapsed 99%CPU (0avgtext+0avgdata 1228maxresident)k
0inputs+0outputs (0major+68minor)pagefaults 0swaps
short
1.38user 0.00system 0:01.38elapsed 99%CPU (0avgtext+0avgdata 1204maxresident)k
0inputs+0outputs (0major+70minor)pagefaults 0swaps
结果相当接近,但随着编译器和编译器设置的不同,它们相对差异很大。
我的结论是,在函数体或签名中选择 int
和 short
s(数组是一个不同的问题),因为一个应该比另一个执行得更好或生成更密集的代码大多是徒劳的(至少在代码中)不固定到具有特定设置的特定编译器)。两者都很快,所以我会选择更适合我的程序语义或更好地传达我的 API 的类型
(如果我期望一个短的正值,不妨在签名中使用 uchar 或 ushort。)
C 程序员倾向于使用int
s,因为 C 在历史上一直偏爱它们(整数文字往往是 int
s,促销往往是 int
s,曾经有隐含的 int 规则用于声明和未声明函数等)和int
s 应该非常适合该架构,但归根结底,具有可读、可维护源代码的密集、高性能机器代码才是最重要的,如果你的理论做某事在源代码中至少没有明显有助于实现这些目标中的一个,我认为这是一个糟糕的理论。
【讨论】:
如果您在微控制器系统上比较这些类型,差异将非常明显。平均 8 位,unsigned char
可能给出一条指令,而unsigned short
给出大约 5 到 10 条指令,unsigned long
大约 100 条指令。
@Lundin 听起来更有理由使用最合适的类型而不是 int
同样enums
被隐式提示为int
s【参考方案3】:
这里有几个问题。
首先char
类型完全不适合保存整数值。它应该只用于容纳字符。这是因为它具有实现定义的签名,char
实际上是不同于signed char
和unsigned char
的不同类型。见Is char signed or unsigned by default?。
如果可能,应该避免使用诸如char
和short
之类的小整数类型的主要原因是隐式类型提升。这些类型受整数提升的影响,这反过来又会导致危险的事情,例如无声更改符号。有关详细信息,请参阅Implicit type promotion rules。
出于这个原因,一些编码标准实际上完全禁止使用较小的整数类型。尽管要使这样的规则可行,您需要一个 32 位或更大的 CPU。因此,如果要考虑各种微控制器,这并不是一个很好的通用解决方案。
另请注意,以这种方式对内存进行微观管理大多只与嵌入式系统编程相关。如果您正在编写 PC 程序,使用较小的类型来节省内存可能是一种“未成熟的优化”。
C 的默认“原始数据类型”,包括char
、short
、int
,总体上是相当不可移植的。移植代码时,它们的大小可能会发生变化,这反过来又会给它们带来不确定的行为。此外,C 允许为这些类型使用各种晦涩和奇异的符号格式,例如补码、符号和幅度、填充位等。
坚固、可移植、高质量的代码根本不使用这些类型,而是使用stdint.h
的类型。作为奖励,该库只允许符合行业标准的二进制补码。
由于上述所有原因,使用较小的整数类型来节省空间并不是一个好主意。同样,stdint.h
更可取。如果您需要一种可移植节省内存的通用类型,除非节省内存意味着降低执行速度,否则请使用int_fast8_t
和类似的。这些将是 8 位,除非使用更大的类型意味着更快的执行。
【讨论】:
以上是关于为啥使用short不好的主要内容,如果未能解决你的问题,请参考以下文章
为啥 Java API 使用 int 而不是 short 或 byte?
如果在三元运算符中使用局部变量,为啥从 int 到 short 的缩小转换不起作用