为什么用短不好
Why is it bad to use short
即使在开发人员保证变量永远不会超过一个字节,有时超过两个字节的脚本中,这也是很常见的; 许多人决定对用于表示 0-1 范围内的数字 nay 的每个可能的变量使用int
类型。
为什么使用char
或short
会这么痛?
我想我听到有人说int
是"更标准"的类型......类型。 这是什么意思。我的问题是,数据类型int
是否比short
(或其他较小的数据类型)有任何定义的优势,因为哪些优点,人们过去几乎总是求助于int
?
作为一般规则,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
有利。
另一方面,如果你有很多不太大的整数要存储(它们的大数组,或者包含不太大的整数的大量结构数组),数据的存储节省可能很大,值得与代码大小的(相对较小的)增加和潜在的速度提高进行权衡。
另请参阅前面的 SO 问题和此 C 常见问题列表条目。
附录:像任何优化问题一样,如果你真的关心数据空间使用、代码空间使用和代码速度,你会希望使用你的机器和处理器进行仔细的测量。 毕竟,您的处理器可能最终不需要任何"额外的转换指令"来转换为较小的类型,因此使用它们可能不是那么大的缺点。 但与此同时,您可能可以确认,对于孤立变量,使用它们也可能不会产生任何可衡量的优势。
<小时 />增编2. 这是一个数据点。 我尝试了代码
extern short a, b, c;
void f()
{
c = a + b;
}
我用两个编译器编译,gcc和clang(为Mac上的英特尔处理器编译)。 然后我short
更改为int
并再次编译。int
- using 代码在 gcc 下小 7 个字节,在 clang 下小 10 个字节。 对汇编语言输出的检查表明,区别在于截断结果以将其存储在c
中;获取short
而不是int
似乎不会改变指令计数。
但是,我随后尝试调用两个不同的版本,发现即使在 10000000000 次调用之后,它在运行时几乎没有任何差异。 因此,答案的"使用short
可能会使代码变大"部分得到了确认,但可能不是"并且还使其变慢"。
这里有几个问题。
-
首先,
char
类型完全不适合保存整数值。它应该仅用于保存字符。这是因为它具有实现定义的符号,char
实际上是独立于signed char
和unsigned char
的不同类型。请参阅默认情况下字符是有符号还是无符号?。 -
但是,应尽可能避免使用小整数类型(如
char
和short
)的主要原因是隐式类型提升。这些类型会受到整数提升的影响,这反过来又可能导致危险的事情,例如符号的静默更改。有关详细信息,请参阅隐式类型升级规则。出于这个原因,一些编码标准实际上完全禁止使用较小的整数类型。尽管要使此类规则可行,但您需要 32 位或更大的 CPU。因此,如果要考虑各种微控制器,它实际上不是一个好的通用解决方案。
另请注意,以这种方式进行微观管理内存主要与嵌入式系统编程相关。如果您正在编写PC程序,则使用较小的类型来节省内存可能是一种"过早的优化"。
-
C的默认"原始数据类型",包括
char
、short
、int
,总体上是不可移植的。移植代码时,它们的大小可能会发生变化,这反过来又会给它们带来不确定的行为。此外,C 允许为这些类型提供各种晦涩和奇特的符号格式,例如一个人的补码、符号和大小、填充位等。坚固、可移植、高质量的代码根本不使用这些类型,而是使用
stdint.h
的类型。作为奖励,该库只允许理智的行业标准二的补充。 -
由于上述所有原因,使用较小的整数类型来节省空间不是一个好主意。同样,
stdint.h
更可取。如果您需要可移植地节省内存的通用类型,除非节省内存意味着降低执行速度,否则请使用int_fast8_t
和类似功能。这些将是 8 位,除非使用更大的类型意味着更快的执行。
我对基于短的代码应该以任何重要方式更慢、更大的说法持怀疑态度(假设这里的局部变量,没有关于大型数组的争议,如果合适,short
肯定会得到回报),所以我试图在我的Intel(R) Core(TM) i5 CPU M 430 @ 2.27GHz
我用了(长.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
)中,用gcc
和clang
-O3
和-Os
构建了程序,并测量了每个函数的100mil调用的大小和运行时。
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);
主.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
应该非常适合架构, 但归根结底,具有可读、可维护源代码的密集、高性能机器代码才是最重要的,如果你在源代码中做某事的理论没有明显有助于实现这些目标中的至少一个,我认为这是一个糟糕的理论。
- 为什么"do while"循环不断退出,即使条件计算结果为 false?
- 为什么在全局范围内使用"extern int a"似乎不行?
- 为什么在popback()操作之后,它仍然打印完整的矢量
- 为什么随机数生成器不在void函数中随机化数字,而在main函数中随机化
- 为什么两个不同的未命名名称空间可以共存于一个cpp文件中
- 为什么会发生堆损坏
- 为什么使用 "this" 指针调用派生成员函数?
- C++我的数学有什么问题,为什么我的代码不能正确循环
- 为什么比较运算符如此快速
- 为什么 Serial.println(<char[]>);返回随机字符?
- 为什么这个运算符<重载函数对 STL 算法不可见?
- 为什么不;名字在地图上是按顺序排列的吗
- 我的字符计数代码计算错误.为什么
- 为什么在没有显式默认构造函数的情况下,将另一个结构封装在联合中作为成员的结构不能编译
- 为什么我的C#代码在调用回C++COM直到Task时会暂停.等待/线程.加入
- 为什么在C++中使用私有复制构造函数与删除复制构造函数
- 为什么野牛仍在使用"int yylex(void)",却找不到"int yylex(YYS
- 为什么 std::unique 不调用 std::sort?
- 既然存在危险,为什么项目要使用-I include开关
- 为什么在运行时没有向我们提供有关分段错误的更多信息?