基本类型与固定宽度类型
面试回答
常见问法
- 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_t 或 off_t | 大文件可能超过 4GB |
| 时间戳 | uint64_t | 不可能为负,范围要大 |
| 像素值 | uint8_t | 0-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
两个原因:
- 元素个数不可能是负数 → 用无符号
- 64 位系统上理论上可以有超过 21 亿个元素(int 装不下)→ 需要 8 字节
标准库里所有表示”大小/个数/长度”的返回值统一用 size_t:vector::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++) {
// 你以为不会进循环?错!
}
一步步拆解:
v.size()返回(size_t)0(size_t)0 - 1→ 无符号整数没有负数,0 减 1 回绕到最大值- 64 位系统上:
0 - 1 = 18446744073709551615(就像里程表 000000 往回拨一格变成 999999) - 循环条件变成
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 → 继续循环
}
最佳实践:
- 不要混用有符号和无符号做比较/运算
- 循环用
int或ptrdiff_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 的所有值)
易错点
- 以为 int 在所有平台都是 4 字节 — 标准不保证,嵌入式可能是 2 字节
- 用 int 存文件大小 — 大于 2GB 的文件会溢出,用
int64_t或off_t v.size() - 1当 v 为空时 — 无符号 0 减 1 回绕成巨大正数- 有符号和无符号比较 —
-1 < 1u为 false - 用 long 做跨平台 8 字节整数 — Windows 上 long 是 4 字节,用
int64_t - 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() 的返回类型。