C数据类型与大小
定义
C的每个对象在编译时就有确定的类型,类型决定三件事:占多少个字节、这串位按什么规则解释、能做哪些运算。变量必须先声明类型再使用。
char、short、int、long、long long是整数类型,float、double是浮点类型,size_t是无符号整数类型,void * 是指针类型。标准只保证大小顺序,不规定具体位数:sizeof(char) 恒为1,且sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long)。具体的位数由平台ABI决定。32位和Windows 64位(ILP32 / LLP64)中long是4字节,Linux与macOS的64位(LP64)中long是8字节。
常见取值如下。大小一列以常见平台为准,写代码时应使用sizeof而不是记住具体数字。
| 类型 | 常见大小 | 有符号范围 | 说明 |
|---|---|---|---|
char | 1 byte | 至少8位;是否有符号由实现决定 | |
short | 2 bytes | ||
int | 4 bytes | 约 亿 | |
long | 4或8 bytes | 随平台 | Windows 64位为4,Linux/macOS 64位为8 |
long long | 8 bytes | 约 | |
float | 4 bytes | 约 位有效数字 | 单精度 |
double | 8 bytes | 约 位有效数字 | 双精度 |
size_t | 4或8 bytes | 无符号 | sizeof的返回类型 |
void * | 4或8 bytes | 无符号 | 指针的字节宽度 |
整数在内存中用补码表示,负数编码与范围见补码与带符号整数运算;浮点数的字段划分与精度见浮点数与IEEE 754。
普通char是否有符号取决于编译器和平台,需要明确范围时应写signed char或unsigned char,不要依赖char的符号性。
性质
溢出与回绕
无符号整数按 取模回绕,这是有定义的行为:unsigned char的 加 得到 。
有符号整数的溢出在标准中是未定义行为,编译器可以假设它不发生,并据此优化代码。典型实现会按补码回绕,例如int的最大值加 得到最小值,但这不能依赖,也不能作为判断溢出的手段。检查有符号加法是否溢出,要在运算前比较:
if (a > INT_MAX - b) { /* a + b会溢出 */ }整数除法向零截断
两个整数相除,结果仍是整数,小数部分直接丢弃,并且是向零截断而不是向下取整:
| 表达式 | C的结果 | Python的对应结果 |
|---|---|---|
5 / 2 | 2 | 2.5(5 // 2为2) |
-5 / 2 | -2 | -3(// 为 -3) |
-5 % 2 | -1 | 1 |
% 的符号随被除数,这是C与Python最容易对不上的地方。要做浮点除法,两个操作数中至少一个必须是浮点类型,例如5 / 2.0。
字面量的进制与前缀
整型字面量的前缀决定它的值:以0开头是八进制,以0x开头是十六进制,十进制没有前缀。
| 字面量 | 十进制值 |
|---|---|
10 | 10 |
010 | 8 |
0x10 | 16 |
0b1010 | 10(C23起,编译器通常作为扩展支持) |
无符号类型用 %u输出,各类型对应的格式符单独整理在C格式化输入输出。
sizeof的性质
sizeof是编译期运算符,返回size_t(无符号),打印时要配 %zu。
sizeof(char)恒为1,C中的“1字节”就是char的大小。sizeof的操作数不会被求值,sizeof(i++)不会让i自增;变长数组是例外。sizeof(数组名)得到整个数组的字节数,sizeof(指针)得到指针本身的字节数,两者含义不同。
与Python的差异
| 方面 | C | Python |
|---|---|---|
| 整数宽度 | 由类型定宽,越界即溢出 | 任意精度,不会溢出 |
| 整数除法 | / 仍得整数,向零截断 | / 得浮点数,// 向下取整 |
| 取模符号 | 随被除数,-5 % 2为 -1 | 随除数,-5 % 2为1 |
| 类型声明 | 先声明类型 | 动态类型 |
| 字符与字符串 | char是1字节整数 | str是Unicode序列,没有单字节字符类型 |
例子
打印各类型的大小
#include <stdio.h>
int main(void) { printf("char=%zu short=%zu int=%zu long=%zu\n", sizeof(char), sizeof(short), sizeof(int), sizeof(long)); printf("long long=%zu float=%zu double=%zu\n", sizeof(long long), sizeof(float), sizeof(double)); printf("size_t=%zu pointer=%zu\n", sizeof(size_t), sizeof(void *)); return 0;}在LP64平台上输出char=1 short=2 int=4 long=8、long long=8 float=4 double=8、size_t=8 pointer=8;在Windows 64位(LLP64)上long是4。
无符号回绕与有符号溢出
#include <stdio.h>#include <limits.h>
int main(void) { unsigned int u = 0; u = u - 1; /* 有定义:回绕成UINT_MAX */ printf("%u\n", u);
int x = INT_MAX; printf("%d\n", x); /* 再加1是未定义行为,不能依赖结果 */ return 0;}第一行输出4294967295(32位unsigned int)。第二处只打印最大值,故意不做 :一旦溢出,标准不再保证结果。
整数除法的两种语言结果
#include <stdio.h>
int main(void) { printf("%d %d %d\n", 5 / 2, -5 / 2, -5 % 2); /* 2 -2 -1 */ return 0;}同样的值在Python里写5 // 2、-5 // 2、-5 % 2,得到2、-3、1。两个语言对“商”的定义不同,做题时按各自语言的规则计算。
探索
同为64位平台,long一定占八字节吗?
比较两个常见数据模型中的类型大小。
正在准备演示…
练习
sizeof(char)的值是 。参考解答
C标准规定
sizeof(char)恒为1,“1字节”就是char的大小。整型字面量
010作为数值是 。参考解答
以
0开头表示八进制,010即 。在32位
int的平台上,-5 / 2的值是 ,-5 % 2的值是 。参考解答
整数除法向零截断,商的整数部分是 ;余数的符号随被除数,所以是 。
size_t类型的值应该用哪个格式符输出?参考解答
size_t是无符号类型,%zu是它对应的长度修饰符与格式符组合;写%d在64位平台上会读错字节。关于C的整数溢出,下列说法正确的是:
参考解答
无符号运算按模 定义,有符号溢出不在标准保证范围内。换成
long只改变位宽,输入足够大时仍会溢出。8位为例,有符号是 ,无符号是 ,个数同为256。