在 C 中与整数类型相关的内容。
整型大小
基本类型数据所占字节数与机器的字长及编译器有关,也就是说,int
long int
short int
的宽度都会随编译器、机器字长而有所区别,例如同样是 32 位操作系统,VC++ 中的 int
类型是 4 字节,而 TuborC 中的 int
类型则是 2 字节。
不过同时几条 ANSI/ISO 制定的规定必须要遵守,一般在 Numerical limits 相关的部分中定义:
sizeof(short int)<=sizeof(int)
;sizeof(int)<=sizeof(long int)
;short int
至少应为 2 字节;long int
至少应为 4 字节;int
建议为一个机器字长,其中 32 位环境为 4 字节,64 位环境为 8 字节。
与基本类型相关的主要包含了几个基本的头文件:
<stdint.h>
一些固定大小的类型,例如uint32_t
int654_t
uintptr_t
等类型;<limits.h>
基本类型的最大最小值限制,例如SHORT_MAX
INT_MAX
LONG_MAX
等;
在机器上具体类型的大小,可以通过如下的代码查看。
#include <float.h>
#include <stdio.h>
#include <limits.h>
void main(void) {
printf("Type Bytes Range\n");
printf(" char %5ld [%d, %d]\n", sizeof(char), CHAR_MIN, CHAR_MAX);
printf(" unsigned char %5ld [%u, %u]\n", sizeof(unsigned char), 0, UCHAR_MAX);
printf(" short %5ld [%d, %d]\n", sizeof(short), SHRT_MIN, SHRT_MAX);
printf(" unsigned short %5ld [%u, %u]\n", sizeof(unsigned short), 0, USHRT_MAX);
printf(" int %5ld [%d, %d]\n", sizeof(int), INT_MIN, INT_MAX);
printf(" unsigned int %5ld [%u, %u]\n", sizeof(unsigned int), 0U, UINT_MAX);
printf(" long %5ld [%ld, %ld]\n", sizeof(long), LONG_MIN, LONG_MAX);
printf(" unsigned long %5ld [%lu, %lu]\n", sizeof(unsigned long), 0UL, ULONG_MAX);
printf(" long long %5ld [%lld, %lld]\n", sizeof(long long), LLONG_MIN, LLONG_MAX);
printf("unsigned long long %5ld [%llu, %llu]\n", sizeof(unsigned long long), 0ULL, ULLONG_MAX);
puts("\n");
printf(" float %5ld [%g, %g]\n", sizeof(float), FLT_MIN, FLT_MAX);
printf(" double %5ld [%g, %g]\n", sizeof(double), DBL_MIN, DBL_MAX);
printf(" long double %5ld [%lg, %lg]\n", sizeof(long double), LDBL_MIN, LDBL_MAX);
}
类型转换
计算机中的数值通过补码进行表示,对于有符号类型,最高有效位代表了符号类型;而无符号类型,最高位仍然是数据位;同时在转换时,还会涉及到不同类型的大小。
所以总共会有八种类型转换。
// <1> 有符号小 --> 有符号大 无损按照符号位扩展
// <2> 无符号小 --> 无符号大 无损零扩展,高位填充零
// <3> 有符号小 --> 无符号大 无损按照符号位扩展
// <4> 无符号小 --> 有符号大 无损零扩展,高位填充零
上述仅列举了部分,规则可以总结为。
- 在由短类型到长类型存储时,不会发生丢失数据,而扩展方式是通过原有的数据类型定义。
- 在由短类型到长类型存储时,如果符号不同,会先进行类型扩展,然后原封不动赋值给对方。
- 当从长类型转换为短类型时,如果高位不都一样,也就意味着高位不只是表示符号位,那么就可能会造成数据丢失。
- 对于同一长度类型,则直接赋值,如果符号不同,那么数值的大小就会发生改变。
- 当整型转换为浮点时,会先转换为 long 类型,再转换为浮点。
另外,注意,当无符号和有符号类型比较时,会先将有符号转换为无符号进行比较,当有符号为负数时,转换之后实际为一个很大的值。
#include <stdio.h>
void main(void) {
char varnchar = 0xff; // -1
char varuchar = 1; // 1
short varnshort = varnchar; // <1> 0xff -> 0xffff
short varushort = varuchar; // <1> 0x01 -> 0x0001
printf("small signed -> large singned %5d %5d\n", varnshort, varushort);
unsigned char uvarnchar = 0xff; // -1
unsigned char uvaruchar = 1; // 1
unsigned short uvarnshort = uvarnchar; // <2> 0xff -> 0x00ff
unsigned short uvarushort = uvaruchar; // <2> 0x01 -> 0x0001
printf("small unsigned -> large unsingned %5u %5u\n", uvarnshort, uvarushort);
unsigned short uvarshort = varnchar; // <3> 0xff -> 0xffff
printf("small signed -> large unsingned %5u\n", uvarshort);
short varshort = uvarnchar; // <4> 0xff -> 0x00ff
printf("small signed -> large unsingned %5d\n", varshort);
}
上面只是基本的概念介绍,下面搞几个题看看。
隐式类型转换
#include <stdio.h>
int main(void)
{
int a = -1;
unsigned int b = 1;
if (a > b)
printf("a > b\n");
else
printf("a <= b\n");
return 0;
}
输出为 a > b
,在比较时,如果没有强制转换,那么默认会转换为无符号类型,而 -1
转换后是一个很大的正整数。
如下是一个比较复杂的部分。
#include <stdio.h>
int main(void) {
unsigned int a = 20;
int b = 13;
int k = b - a;
printf("k = %d, %u\n", k, b - a);
if (k < (unsigned int)b + a)
printf("false A 4294967289 < 13 + 20\n");
if (k < (int)(b + a))
printf("true B -7 < 13 + 20\n");
if (k < b + (int)a)
printf("true C -7 < 13 + 20\n");
if (k < (b + a))
printf("false D 4294967289 < 13 + 20\n");
return 0;
}
首先 k = b - a
的计算实际上等价于 k = (int)((unsigned int)b - (unsigned int)a) = (int)(4294967289) = -7
,也就是实际计算是无符号类型,最后将结果强转为有符号类型。
整型溢出
简单来说,就是在计算的时候超过了整数表示的最大范围,分为了有符号和无符号类型,两者在定义是略有区别。
- 有符号类型,C 语言规范未定义,不同的编译器实现会略有区别。
- 无符号类型,约束为对溢出结果按照变量类型大小取模。
如下是在 GCC 上的简单示例。
#include <stdio.h>
int main(void) {
unsigned char a = 0xff; // 255 UCHAR_MAX
printf("%d\n", ++a); // 256 mod 2^8 = 0
signed char b = 0x7f; // 127 SCHAR_MAX
printf("%d\n", ++b); // 0x80 -> -128
return 0;
}
注意,如果是乘法,例如 0x7f * 0x05
那么结果就是 123
了。
示例
下面那些函数可能会出现整型溢出。
int funcA(short a, short b) {
return (a + b);
}
int funcB(signed char a, signed char b) {
return (a + b);
}
long long int funcC(int a, int b) {
return (a + b);
}
long long int funcD(int a, int b) {
return ((long long int)a + b);
}
short
相加时会自动转换为 int
类型计算,而 int
相加是不会自动扩展为 long long int
的,需要显示转换。