知识点

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而不是记住具体数字。

类型常见大小有符号范围说明
char1 byte−128∼127-128\sim 127至少8位;是否有符号由实现决定
short2 bytes−32768∼32767-32768\sim 32767
int4 bytes−231∼231−1-2^{31}\sim 2^{31}-1约 ±21\pm 21 亿
long4或8 bytes随平台Windows 64位为4,Linux/macOS 64位为8
long long8 bytes−263∼263−1-2^{63}\sim 2^{63}-1约 ±9×1018\pm 9\times 10^{18}
float4 bytes约 6∼76\sim 7 位有效数字单精度
double8 bytes约 15∼1615\sim 16 位有效数字双精度
size_t4或8 bytes无符号sizeof的返回类型
void *4或8 bytes无符号指针的字节宽度

整数在内存中用补码表示,负数编码与范围见补码与带符号整数运算;浮点数的字段划分与精度见浮点数与IEEE 754。

普通char是否有符号取决于编译器和平台,需要明确范围时应写signed char或unsigned char,不要依赖char的符号性。

性质

溢出与回绕

无符号整数按 2n2^n 取模回绕,这是有定义的行为:unsigned char的 255255 加 11 得到 00。

有符号整数的溢出在标准中是未定义行为,编译器可以假设它不发生,并据此优化代码。典型实现会按补码回绕,例如int的最大值加 11 得到最小值,但这不能依赖,也不能作为判断溢出的手段。检查有符号加法是否溢出,要在运算前比较:

检查加法是否溢出
if (a > INT_MAX - b) { /* a + b会溢出 */ }

整数除法向零截断

两个整数相除,结果仍是整数,小数部分直接丢弃,并且是向零截断而不是向下取整:

表达式C的结果Python的对应结果
5 / 222.5(5 // 2为2)
-5 / 2-2-3(// 为 -3)
-5 % 2-11

% 的符号随被除数,这是C与Python最容易对不上的地方。要做浮点除法,两个操作数中至少一个必须是浮点类型,例如5 / 2.0。

字面量的进制与前缀

整型字面量的前缀决定它的值:以0开头是八进制,以0x开头是十六进制,十进制没有前缀。

字面量十进制值
1010
0108
0x1016
0b101010(C23起,编译器通常作为扩展支持)

无符号类型用 %u输出,各类型对应的格式符单独整理在C格式化输入输出。

sizeof的性质

sizeof是编译期运算符,返回size_t(无符号),打印时要配 %zu。

  • sizeof(char) 恒为1,C中的“1字节”就是char的大小。
  • sizeof的操作数不会被求值,sizeof(i++) 不会让i自增;变长数组是例外。
  • sizeof(数组名) 得到整个数组的字节数,sizeof(指针) 得到指针本身的字节数,两者含义不同。

与Python的差异

方面CPython
整数宽度由类型定宽,越界即溢出任意精度,不会溢出
整数除法/ 仍得整数,向零截断/ 得浮点数,// 向下取整
取模符号随被除数,-5 % 2为 -1随除数,-5 % 2为1
类型声明先声明类型动态类型
字符与字符串char是1字节整数str是Unicode序列,没有单字节字符类型

例子

打印各类型的大小

sizes.c
#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。

无符号回绕与有符号溢出

overflow.c
#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)。第二处只打印最大值,故意不做 +1+1:一旦溢出,标准不再保证结果。

整数除法的两种语言结果

division.c
#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一定占八字节吗?

比较两个常见数据模型中的类型大小。

正在准备演示…

题集

练习

  1. sizeof(char) 的值是 。

    参考解答

    C标准规定sizeof(char) 恒为1,“1字节”就是char的大小。

  2. 整型字面量010作为数值是 。

    参考解答

    以0开头表示八进制,010即 1×8+0=81\times 8+0=8。

  3. 在32位int的平台上,-5 / 2的值是 ,-5 % 2的值是 。

    参考解答

    整数除法向零截断,商的整数部分是 −2-2;余数的符号随被除数,所以是 −1-1。

  4. size_t类型的值应该用哪个格式符输出?

    选择答案
    参考解答

    size_t是无符号类型,%zu是它对应的长度修饰符与格式符组合;写 %d在64位平台上会读错字节。

  5. 关于C的整数溢出,下列说法正确的是:

    题 overflow-statements · choice
    参考解答

    无符号运算按模 2n2^n 定义,有符号溢出不在标准保证范围内。换成long只改变位宽,输入足够大时仍会溢出。8位为例,有符号是 −128∼127-128\sim 127,无符号是 0∼2550\sim 255,个数同为256。