bit、Byte 与 C 语言字节
bit(比特、位) 是最小的信息单位,只能表示 0 或 1。例如,二进制数 10110110 有 8 个 bit,各位权值从左到右为 128、64、32、16、8、4、2、1,因此它表示:
128 + 32 + 16 + 4 + 2 = 182
Byte(字节) 是常用的存储单位。现代计算机通常以 8 bit 组成 1 Byte;注意大小写:b 常表示 bit,B 常表示 Byte。因此 8 Mb = 1 MB:前者是 8 megabit,后者是 1 megabyte,换算后数值相等。二进制单位通常写作 Mib 和 MiB,同样有 8 Mib = 1 MiB。
C 语言中的一个字节由 sizeof(char) 定义为 1 个 C 字节,但一个 C 字节不保证恰好是 8 bit。可用 CHAR_BIT 查看每字节的位数:
#include <limits.h>
#include <stdio.h>
int main(void)
{
printf("%d\n", CHAR_BIT);
}
常见结果是 8。sizeof 返回的是 C 字节数,不是 bit 数。
C 语言的整数类型
常见整数类型包括 char、short、int、long 和 long long,除 char 的符号性另有规则外,其余整数类型可带 signed 或 unsigned。标准规定这些类型的存储宽度按顺序不递减,但具体大小由实现决定;常见平台上的大小如下:
| 类型 | 常见大小 | 常见位数 |
|---|---|---|
char |
1 Byte | 8 bit |
short |
2 Byte | 16 bit |
int |
4 Byte | 32 bit |
long |
4 或 8 Byte | 32 或 64 bit |
long long |
8 Byte | 64 bit |
上表是常见情况,不是所有平台的保证。需要确认当前实现时,可以查看 sizeof(type) 和 <limits.h> 中的范围常量。
定宽整数类型
需要明确整数宽度时,可以使用 <stdint.h> 中的类型,例如 int32_t 和 uint32_t。这些确切宽度类型在实现提供相应宽度时才会定义;若希望表达“至少 N 位”,可以使用 int_leastN_t / uint_leastN_t,例如 int_least32_t。
#include <stdint.h>
int32_t count = 100;
uint32_t flags = 0;
char
char a;
signed char b;
unsigned char c;
char 占 1 个 C 字节;普通 char 是有符号还是无符号由实现决定,不能直接假设它等同于 signed char。若一个字节为 8 bit,常见范围是:signed char 为 -128 ~ 127,unsigned char 为 0 ~ 255。8 bit 有 2^8 = 256 种位模式。
short 与 int
short、short int、signed short 等写法均合法。常见 short 为 16 bit,范围通常是 -32768 ~ 32767(有符号)或 0 ~ 65535(无符号)。
int 是最常用的整数类型;int 等价于 signed int。常见 int 为 32 bit,范围为 -2^31 ~ 2^31 - 1;对应的 unsigned int 范围为 0 ~ 2^32 - 1。
long 与 long long
long 的大小随平台而异,常见为 4 或 8 Byte,不能认为它一定是 8 Byte。long long 至少为 64 bit,常见为 8 Byte;常见 64 位有符号范围约为 -9.22 × 10^18 ~ 9.22 × 10^18。
整数类型转换
转换可以由编译器隐式完成,也可以由程序员通过显式类型转换指定。转换是否安全,取决于目标类型能否表示源值;“类型更大”只是初步判断,不能替代对范围和规则的分析。
隐式转换与窄化
将值转换到能表示该值的类型时,值保持不变,例如:
short a = 100;
int b = a;
从较宽类型转为较窄类型时,如果目标类型无法表示源值,结果可能改变,这称为窄化转换:
int a = 300;
unsigned char b = a;
若 unsigned char 有 8 bit,其转换结果按模 2^8 计算,故 b 为 300 % 256 = 44。有符号目标类型无法表示源值时,结果由实现定义(C 标准版本不同还涉及实现定义信号);不要套用无符号的取模规则。
整数运算溢出
整数运算的结果超出类型可表示范围时,行为取决于类型:
- 无符号整数:按模
最大值 + 1回绕。例如,若unsigned int为 32 bit,UINT_MAX + 1u的结果为0。 - 有符号整数:若算术结果超出可表示范围,行为未定义,不能按补码回绕或取模来推算。比如
INT_MAX + 1不保证得到INT_MIN。
#include <limits.h>
unsigned int u = UINT_MAX;
u = u + 1u; // 结果为 0
int x = INT_MAX;
// x = x + 1; // 有符号溢出,行为未定义
这与“把一个整数值转换为另一种类型”是两回事:无符号算术按模运算;转为无符号类型时也按目标范围取模;有符号算术溢出则没有可依赖的结果。
signed 与 unsigned 转换
转换到无符号类型时,结果按目标类型的取值范围取模。若 unsigned int 有 32 bit:
int a = -1;
unsigned int b = a;
则 b 为 2^32 - 1,即 4294967295。因此,负数转为无符号数时可能变成很大的正数。
整型提升
在多数算术表达式中,char 和 short 会先进行整型提升。若 int 能表示该类型的所有值,就提升为 int;否则提升为 unsigned int。例如:
char a = 10;
char b = 20;
printf("%d\n", a + b);
常见实现中,a 和 b 先提升为 int,再进行加法,表达式结果为 int。所以 char + char 通常不是以 char 类型完成运算。
不同整数类型混合运算
混合类型运算会先进行整数提升,再按通常算术转换规则把操作数转换到共同类型。简单情况下,较窄类型会转为较宽类型,例如 short + int 通常以 int 运算、int + long long 通常以 long long 运算;涉及 unsigned 时,共同类型的选择还取决于各类型的转换等级和范围,不能只看哪个“更大”。
典型陷阱是有符号数与无符号数比较:
#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;
}
在常见的 int 与 unsigned int 同为 32 bit 的实现中,a 会转换为 unsigned int,值变成 4294967295,因此比较为假,输出 a >= b。实际判断前应先确认两侧的类型和转换结果。
显式类型转换
显式转换的语法是 (目标类型)表达式:
int a = 10;
long long b = (long long)a;
也可以写成 (unsigned char)a。显式转换只明确要求转换,不会自动避免越界或信息丢失。
printf 格式说明符
printf 是可变参数函数,格式说明符必须与传入参数的实际类型匹配;不匹配会导致未定义行为。常用对应关系如下:
| 类型 | printf 格式说明符 |
|---|---|
int |
%d 或 %i |
unsigned int |
%u |
long / unsigned long |
%ld / %lu |
long long / unsigned long long |
%lld / %llu |
size_t |
%zu |
定宽整数类型的底层类型可能随实现而异,应使用 <inttypes.h> 提供的格式宏,而不要假定 int32_t 一定对应 int:
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int32_t count = 100;
uint32_t flags = 7;
printf("%" PRId32 "\n", count);
printf("%" PRIu32 "\n", flags);
其中 PRId32 和 PRIu32 分别对应有符号和无符号 32 位整数的输出格式。
复习要点
bit是二进制位;现代系统通常8 bit = 1 Byte。C 字节的位数可用CHAR_BIT查询。sizeof的结果单位是 C 字节,例如sizeof(int)表示int占用的字节数。- 需要明确整数宽度时可查看
<stdint.h>中的定宽或最小宽度类型;输出时用匹配的格式说明符,定宽类型优先使用<inttypes.h>格式宏。 - 整数类型的常见大小不是跨平台保证;普通
char的符号性也由实现决定。 char、short参与算术运算前通常会进行整型提升,常见情况下提升为int。- 转换到无符号类型按目标范围取模;无法表示的值转换到有符号类型时不要假定为取模结果。
- 混合
signed和unsigned运算时,先分析整型提升和共同类型,避免仅凭直觉判断。 - 无符号整数算术溢出会按模回绕;有符号整数算术溢出是未定义行为。