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 运算时,先分析整型提升和共同类型,避免仅凭直觉判断。
  • 无符号整数算术溢出会按模回绕;有符号整数算术溢出是未定义行为。