blueyi's notes

Follow Excellence,Success will chase you!

0%

C/C++拾遗之关于固定大小的整型

由于历史上考虑到不同机器架构在处理不同位宽的整型时性能不同,所以当时的标准要求保证只要不小于某一宽度即可,具体多宽可由编译器自行决定,导致了在使用int或long时在不同机器上可能存在表现不同的情况。这样也大大降低了程序的稳定性和可移植性。C99标准中定义一组固定位宽的整型,在头文件stdint.h中,建议在编程中无特殊原因都使用这些固定位宽的整型,以保证程序在不同的机器构架下整型变量的大小依然相同。对应于C++11中相应的固定位宽的头文件是cstdin。所涉及到的类型主要有:

Name Type Range
int8_t 1 byte signed -128 to 127
uint8_t 1 byte unsigned 0 to 255
int16_t 2 byte signed -32,768 to 32,767
uint16_t 2 byte unsigned 0 to 65,535
int32_t 4 byte signed -2,147,483,648 to 2,147,483,647
uint32_t 4 byte unsigned 0 to 4,294,967,295
int64_t 8 byte signed -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
uint64_t 8 byte unsigned 0 to 18,446,744,073,709,551,615

很好记,就是int或uint后面跟上所需要的位宽,然后后面再加个_t即可
查看了下linux下的头文件,发现有如下一些typedef:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

/* Signed. */

/* There is some amount of overlap with <sys/types.h> as known by inet code */
#ifndef __int8_t_defined
# define __int8_t_defined
typedef signed char int8_t;
typedef short int int16_t;
typedef int int32_t;
# if __WORDSIZE == 64
typedef long int int64_t;
# else
__extension__
typedef long long int int64_t;
# endif
#endif

/* Unsigned. */
typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
#ifndef __uint32_t_defined
typedef unsigned int uint32_t;
# define __uint32_t_defined
#endif
#if __WORDSIZE == 64
typedef unsigned long int uint64_t;
#else
__extension__
typedef unsigned long long int uint64_t;
#endif

用法就是直接包含头文件,然后定义使用即可:

1
2
3
4
5
6
7
8
9
#include <cstdint>
#include <iostream>

int main(void)
{
int16_t i(5);
std::cout << i << std::endl;
return 0;
}

C++中关于整型的使用建议是:

  • 仅在整型变量的大小无关并且该变量不会变大时才使用int
  • 通常情况下都应该考试使用固定宽度的整型,特别是当某个整型变量需要保证宽度的时候
  • 只有当你有一个令人信服的理由时才使用无符号类型

当unsigned类的整型与signed整型混合使用时非常容易出问题(我自己写程序时遇到这个情况,debug好久才发现)如:

1
2
3
4
5
6
7
8
void doSoming(unsigned int x)
{
//Run some code
}
int main(void)
{
doSoming(-1);
}

上面传递给函数中的-1有可能会变成非常大的数字。
所以编程中如果没有特别需要,最好避免使用unsigned类型。

一个小插曲
考虑练习下这些类型区别时,出现了一个让自己百思不得其解的问题,看如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <cstdint>

int main(void)
{
int8_t i8 = 8;
int16_t i16 = -16;
int32_t i32 = 32;
int64_t i64= 64;
std::cout << "==" << i8 << "==" << std::endl;
std::cout << "==" << i8 << i16 << i32 << i64;
return 0;
}

我的第一反应是输出中i8的输出应该是8,结果却发现输出中第一行只有3个等号。很费解,gdb调试查看变量情况,gdb给的提示是i8 = 8 '\b',我依然没有反应过来哪里有问题(唉,这是有多久没有用char了)。然后开始查看头文件中对int8_t的定义,发现了上述头文件,原来这货就是个有符号的char,然后印象中char不就是可以用来存int吗,而且它俩在很多情况下是可以无损转换的,为什么输出不是8呢。这才开始查char的资料,突然想起来ASCII码的事,char在内存中确实是以整型存储,但存储的是ASCII码的整型,而8的ASCII码是’\b’(表示退格)。注意对于char来说,它只能存储一个(对于转义字符只算一个字符)使用单引号括起来的字符,将一个整数赋给它实际上发生了类型转换,char会保留该整数的低8位,存储为相应ASCII码的二进制。如:std::cout << int('\b');将会输出整数十进制的8;std::cout << char(8);将会输出一个退格(虽然看不见),因为十进制ASCII码8对应的是退格;std::cout << int('8');将会输出56,因为字符’8’对应的ASCII码的十进制是56;std::cout << char('8');将输出一个字符8,当然本身就是个字符,是否强制转换并无影响。
总之,代码要天天敲,基础知识要经常回顾,不然忘的太快了。

参考:Fixed-width integers and the unsigned controversy

Welcome to my other publishing channels