⚡C++ 语言基础

基本类型与固定宽度类型

面试回答

常见问法

  • int 是几个字节?一定是 4 字节吗?
  • int32_t 和 int 有什么区别?
  • 什么时候用 unsigned?有符号和无符号混用有什么坑?
  • size_t 是什么?为什么不用 int?

回答

C++ 标准只规定了基本类型的最小范围,没规定具体字节数。主流平台(x86_64、ARM64)上 int 是 4 字节,但标准不保证。需要精确控制大小时用 <cstdint> 里的固定宽度类型(int32_t、uint64_t 等)。

有符号和无符号的选择取决于语义:值不可能为负就用 unsigned(如文件大小、权限、时间戳),可能为负就用 signed(如偏移量、温度)。混用时要注意隐式转换陷阱。


追问

各平台上基本类型的大小

                    LP32    ILP32    LLP64     LP64
                  (旧嵌入式) (Win32)  (Win64)  (Linux64/macOS64/ARM64)
char               1        1        1         1
short              2        2        2         2
int                2        4        4         4
long               4        4        4         8  ← 这个差异最大!
long long          -        8        8         8
指针               2        4        8         8

关键记忆:
- int 在现代平台都是 4 字节
- long 在 Windows 64 位是 4 字节,Linux/macOS 64 位是 8 字节
- 指针大小 = 地址总线宽度(32 位系统 4 字节,64 位系统 8 字节)

面试陷阱sizeof(long) 在 Windows 和 Linux 上不一样!跨平台代码不要用 long 表示”8 字节整数”,用 int64_t


32/64 位系统与类型大小的关系

常见误区:以为 32 位系统上所有类型最大只能 4 字节。错!

32 位系统上:
  int         = 4 字节
  double      = 8 字节  ← 照样 8 字节!
  long long   = 8 字节  ← 也是 8 字节!
  指针        = 4 字节  ← 只有这个受 32 位限制

64 位系统上:
  int         = 4 字节  ← 没变!
  double      = 8 字节
  long long   = 8 字节
  指针        = 8 字节  ← 这个变大了

32/64 位到底决定了什么

  • 指针大小(能寻址多少内存:32 位 → 4GB,64 位 → 16EB)
  • 通用寄存器宽度(一条指令能搬多少数据)

不决定什么

  • 数据类型的大小。double 永远 8 字节,long long 永远 8 字节
  • CPU 处理大数据的能力。32 位 CPU 处理 8 字节数据只是多跑几条指令,慢一点而已
  • 浮点类型。浮点走 FPU/SSE 专用寄存器,和通用寄存器宽度无关

固定宽度类型(<cstdint>

#include <cstdint>

// 有符号
int8_t    a;  // 精确 1 字节,-128 ~ 127
int16_t   b;  // 精确 2 字节,-32768 ~ 32767
int32_t   c;  // 精确 4 字节,-21亿 ~ 21亿
int64_t   d;  // 精确 8 字节,-922京 ~ 922京

// 无符号
uint8_t   e;  // 精确 1 字节,0 ~ 255
uint16_t  f;  // 精确 2 字节,0 ~ 65535
uint32_t  g;  // 精确 4 字节,0 ~ 42亿
uint64_t  h;  // 精确 8 字节,0 ~ 1844京

什么时候用哪个

场景用什么原因
循环计数、临时变量int不在乎精确大小,编译器优化友好
网络协议字段uint32_t必须精确,对端要按字节解析
文件格式int32_t写入磁盘的数据必须大小确定
数组下标、容器大小size_t标准库的约定,保证够大
文件大小uint64_toff_t大文件可能超过 4GB
时间戳uint64_t不可能为负,范围要大
像素值uint8_t0-255,刚好 1 字节

size_t 是什么?

// size_t 的定义(简化)
// 32 位平台:typedef unsigned int size_t;      → 4 字节
// 64 位平台:typedef unsigned long size_t;     → 8 字节

size_t 是”当前平台能表示的最大对象大小”的类型。

怎么理解?32 位系统最大寻址 4GB 内存,一个对象不可能比整个内存还大,所以用 4 字节无符号整数就够表示任何对象的大小了。64 位系统同理,用 8 字节。简单说:size_t 的宽度 = 指针的宽度 = 你能访问的最大内存范围。

为什么 v.size() 返回 size_t

std::vector<int> v;
auto n = v.size();  // 返回类型是 size_t

两个原因:

  1. 元素个数不可能是负数 → 用无符号
  2. 64 位系统上理论上可以有超过 21 亿个元素(int 装不下)→ 需要 8 字节

标准库里所有表示”大小/个数/长度”的返回值统一用 size_tvector::size()string::length()sizeof 的返回值、strlen() 的返回值,全是。

经典坑:v.size() - 1 当 v 为空时

std::vector<int> v;  // 空的,v.size() 返回 0

for (size_t i = 0; i < v.size() - 1; i++) {
    // 你以为不会进循环?错!
}

一步步拆解:

  1. v.size() 返回 (size_t)0
  2. (size_t)0 - 1 → 无符号整数没有负数,0 减 1 回绕到最大值
  3. 64 位系统上:0 - 1 = 18446744073709551615(就像里程表 000000 往回拨一格变成 999999)
  4. 循环条件变成 i < 18446744073709551615 → 循环 184 亿亿次 → 程序卡死或越界崩溃

正确写法

// 方法 1:先判断空
if (!v.empty()) {
    for (size_t i = 0; i < v.size() - 1; i++) { ... }
}

// 方法 2:换个写法避免减法(推荐)
for (size_t i = 0; i + 1 < v.size(); i++) { ... }

// 方法 3:直接用范围 for(最安全)
for (auto& item : v) { ... }

为什么不用 int 存文件大小:如果容器有超过 21 亿个元素(64 位平台理论上可以),int 装不下。size_t 保证在当前平台上够大。


有符号 vs 无符号的陷阱

陷阱 1:混合比较

int a = -1;
unsigned int b = 1;

if (a < b) {
    // 你以为会进这里?不会!
    // -1 被隐式转换为 unsigned → 变成 4294967295(超大正数)
    // 4294967295 < 1 为 false
}

陷阱 2:无符号减法下溢

unsigned int x = 3;
unsigned int y = 5;
unsigned int result = x - y;  // 不是 -2,而是 4294967294(回绕了)

// 更常见的坑:
std::vector<int> v;  // 空 vector
for (size_t i = 0; i < v.size() - 1; i++) {  // v.size() 是 0
    // 0 - 1 对 unsigned 来说 = 巨大正数 → 循环爆炸
}

陷阱 3:循环条件

for (unsigned int i = 10; i >= 0; i--) {
    // 永远不会结束!unsigned 永远 >= 0
    // i 减到 0 再减 1 → 变成 4294967295 → 继续循环
}

最佳实践

  • 不要混用有符号和无符号做比较/运算
  • 循环用 intptrdiff_t,别用 unsigned 做递减循环
  • 容器遍历用范围 for 或迭代器,避免 size() - 1 的坑

隐式转换规则(整型提升)

当有符号和无符号混合运算时,编译器的转换规则:

1. 如果两个操作数类型相同 → 不转换
2. 如果一个是 unsigned,另一个是 signed,且 unsigned 的 rank ≥ signed
   → signed 转成 unsigned(这就是坑的来源!)
3. 如果 signed 类型能表示 unsigned 的所有值
   → unsigned 转成 signed(安全)
4. 否则 → 都转成 unsigned

实际效果:
  int + unsigned int → unsigned int(int 被转成 unsigned)
  int + unsigned short → int(int 能装下 unsigned short 的所有值)

易错点

  1. 以为 int 在所有平台都是 4 字节 — 标准不保证,嵌入式可能是 2 字节
  2. 用 int 存文件大小 — 大于 2GB 的文件会溢出,用 int64_toff_t
  3. v.size() - 1 当 v 为空时 — 无符号 0 减 1 回绕成巨大正数
  4. 有符号和无符号比较-1 < 1u 为 false
  5. 用 long 做跨平台 8 字节整数 — Windows 上 long 是 4 字节,用 int64_t
  6. printf 格式符不匹配%d 打印 uint64_t 是未定义行为,用 PRIu64

记忆技巧

类型选择口诀:
  随便用 → int
  要精确 → int32_t / uint64_t
  要够大 → size_t / ptrdiff_t
  不为负 → unsigned 系列

固定宽度类型命名规律:
  [u]int[位数]_t
  u = unsigned
  位数 = 8/16/32/64
  _t = type 的缩写

  uint32_t = unsigned + 32位 + type = 4字节无符号整数

面试速答版

Q: int 一定是 4 字节吗?

主流平台是,但 C++ 标准只保证 int ≥ 16 位。需要精确大小用 int32_t

Q: int32_t 和 int 区别?

int32_t 保证精确 4 字节,跨平台不变;int 大小由平台决定。网络协议、文件格式等需要精确大小的场景必须用固定宽度类型。

Q: 什么时候用 unsigned?

值语义上不可能为负时用(文件大小、权限、时间戳)。但注意不要和 signed 混合运算,会有隐式转换陷阱。

Q: size_t 是什么?

无符号整数类型,大小等于当前平台指针宽度(32 位系统 4 字节,64 位系统 8 字节)。是 sizeof 的返回类型和容器 .size() 的返回类型。

Related · 语言基础